diff --git a/crates/moonbuild/src/new.rs b/crates/moonbuild/src/new.rs index d744a5da..2df092b2 100644 --- a/crates/moonbuild/src/new.rs +++ b/crates/moonbuild/src/new.rs @@ -141,8 +141,8 @@ fn common( ) -> anyhow::Result { std::fs::create_dir_all(target_dir).context("failed to create target directory")?; - if !is_in_git_repo(target_dir) { - git_init_repo(target_dir); + if !is_in_git_repo(target_dir)? { + git_init_repo(target_dir)?; } { diff --git a/crates/mooncake/src/update.rs b/crates/mooncake/src/update.rs index 21188cb3..4743f0e8 100644 --- a/crates/mooncake/src/update.rs +++ b/crates/mooncake/src/update.rs @@ -18,97 +18,204 @@ use std::path::Path; -use anyhow::bail; use colored::Colorize; -use moonutil::mooncakes::RegistryConfig; +use moonutil::{ + git::{GitCommandError, Stdios}, + mooncakes::RegistryConfig, +}; + +#[derive(Debug, thiserror::Error)] +#[error("failed to clone registry index")] +struct CloneRegistryIndexError { + #[source] + source: CloneRegistryIndexErrorKind, +} + +#[derive(Debug, thiserror::Error)] +enum CloneRegistryIndexErrorKind { + #[error(transparent)] + GitCommandError(#[from] GitCommandError), + + #[error(transparent)] + IO(#[from] std::io::Error), + + #[error("non-zero exit code: {0}")] + NonZeroExitCode(std::process::ExitStatus), +} fn clone_registry_index( registry_config: &RegistryConfig, target_dir: &Path, -) -> anyhow::Result { - let output = std::process::Command::new("git") - .arg("clone") - .arg(®istry_config.index) - .arg(target_dir) - .spawn()? - .wait(); - match output { - Ok(status) => { - if !status.success() { - bail!("Failed to clone registry index"); - } - Ok(0) - } - Err(e) => { - eprintln!("Failed to clone registry index: {}", e); - bail!("Failed to clone registry index"); - } +) -> Result<(), CloneRegistryIndexError> { + let mut child = moonutil::git::git_command( + &[ + "clone", + ®istry_config.index, + target_dir.to_str().unwrap(), + ], + Stdios::npp(), + ) + .map_err(|e| CloneRegistryIndexError { + source: CloneRegistryIndexErrorKind::GitCommandError(e), + })?; + + let status = child.wait().map_err(|e| CloneRegistryIndexError { + source: CloneRegistryIndexErrorKind::IO(e), + })?; + if !status.success() { + return Err(CloneRegistryIndexError { + source: CloneRegistryIndexErrorKind::NonZeroExitCode(status), + }); } + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +#[error("failed to pull latest registry index")] +struct PullLatestRegistryIndexError { + #[source] + source: PullLatestRegistryIndexErrorKind, +} + +#[derive(Debug, thiserror::Error)] +enum PullLatestRegistryIndexErrorKind { + #[error(transparent)] + GitCommandError(GitCommandError), + + #[error(transparent)] + IO(#[from] std::io::Error), + + #[error("non-zero exit code: {0}")] + NonZeroExitCode(std::process::ExitStatus), } fn pull_latest_registry_index( _registry_config: &RegistryConfig, target_dir: &Path, -) -> anyhow::Result { - let output = std::process::Command::new("git") - .arg("pull") - .arg("origin") - .arg("main") - .current_dir(target_dir) - .spawn()? - .wait()?; - - match output.code() { - Some(code) => { - if code != 0 { - bail!("Failed to pull registry index"); - } - Ok(0) - } - None => { - eprintln!("Failed to pull registry index"); - bail!("Failed to pull registry index"); - } +) -> Result<(), PullLatestRegistryIndexError> { + let mut child = moonutil::git::git_command( + &["-C", target_dir.to_str().unwrap(), "pull", "origin", "main"], + Stdios::npp(), + ) + .map_err(|e| PullLatestRegistryIndexError { + source: PullLatestRegistryIndexErrorKind::GitCommandError(e), + })?; + let status = child.wait().map_err(|e| PullLatestRegistryIndexError { + source: PullLatestRegistryIndexErrorKind::IO(e), + })?; + if !status.success() { + return Err(PullLatestRegistryIndexError { + source: PullLatestRegistryIndexErrorKind::NonZeroExitCode(status), + }); } + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +#[error("update failed")] +struct UpdateError { + #[source] + source: UpdateErrorKind, +} + +#[derive(Debug, thiserror::Error)] +enum UpdateErrorKind { + #[error(transparent)] + CloneRegistryIndexError(#[from] CloneRegistryIndexError), + + #[error(transparent)] + PullLatestRegistryIndexError(#[from] PullLatestRegistryIndexError), + + #[error(transparent)] + GetRemoteUrlError(#[from] GetRemoteUrlError), + + #[error(transparent)] + IO(#[from] std::io::Error), +} + +#[derive(Debug, thiserror::Error)] +#[error("failed to get remote url")] +struct GetRemoteUrlError { + #[source] + source: GetRemoteUrlErrorKind, +} + +#[derive(Debug, thiserror::Error)] +enum GetRemoteUrlErrorKind { + #[error(transparent)] + GitCommandError(#[from] GitCommandError), + + #[error(transparent)] + IO(#[from] std::io::Error), +} + +fn get_remote_url(target_dir: &Path) -> Result { + let output = moonutil::git::git_command( + &[ + "-C", + target_dir.to_str().unwrap(), + "remote", + "get-url", + "origin", + ], + Stdios::npp(), + ) + .map_err(|e| GetRemoteUrlError { + source: GetRemoteUrlErrorKind::GitCommandError(e), + })? + .wait_with_output() + .map_err(|e| GetRemoteUrlError { + source: GetRemoteUrlErrorKind::IO(e), + })?; + let url = String::from_utf8_lossy(&output.stdout).trim().to_string(); + Ok(url) } pub fn update(target_dir: &Path, registry_config: &RegistryConfig) -> anyhow::Result { if target_dir.exists() { - let output = std::process::Command::new("git") - .arg("remote") - .arg("get-url") - .arg("origin") - .current_dir(target_dir) - .output()?; - - let url = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let url = get_remote_url(target_dir).map_err(|e| UpdateError { + source: UpdateErrorKind::GetRemoteUrlError(e), + })?; if url == registry_config.index { let result = pull_latest_registry_index(registry_config, target_dir); - if result.is_err() { - eprintln!( - "Failed to update registry, {}", - "re-cloning".bold().yellow() - ); - std::fs::remove_dir_all(target_dir)?; - clone_registry_index(registry_config, target_dir)?; - eprintln!("{}", "Registry index re-cloned successfully".bold().green()); - Ok(0) - } else { - eprintln!("{}", "Registry index updated successfully".bold().green()); - Ok(0) + match result { + Err(_) => { + eprintln!( + "failed to update registry, {}", + "re-cloning".bold().yellow() + ); + std::fs::remove_dir_all(target_dir).map_err(|e| UpdateError { + source: UpdateErrorKind::IO(e), + })?; + clone_registry_index(registry_config, target_dir).map_err(|e| UpdateError { + source: UpdateErrorKind::CloneRegistryIndexError(e), + })?; + eprintln!("{}", "Registry index re-cloned successfully".bold().green()); + Ok(0) + } + Ok(()) => { + eprintln!("{}", "Registry index updated successfully".bold().green()); + Ok(0) + } } } else { eprintln!( "Registry index is not cloned from the same URL, {}", "re-cloning".yellow().bold() ); - std::fs::remove_dir_all(target_dir)?; - clone_registry_index(registry_config, target_dir)?; + std::fs::remove_dir_all(target_dir).map_err(|e| UpdateError { + source: UpdateErrorKind::IO(e), + })?; + clone_registry_index(registry_config, target_dir).map_err(|e| UpdateError { + source: UpdateErrorKind::CloneRegistryIndexError(e), + })?; eprintln!("{}", "Registry index re-cloned successfully".bold().green()); Ok(0) } } else { - clone_registry_index(registry_config, target_dir)?; + clone_registry_index(registry_config, target_dir).map_err(|e| UpdateError { + source: UpdateErrorKind::CloneRegistryIndexError(e), + })?; eprintln!("{}", "Registry index cloned successfully".bold().green()); Ok(0) } diff --git a/crates/moonutil/src/git.rs b/crates/moonutil/src/git.rs index eed0a69e..ca62f327 100644 --- a/crates/moonutil/src/git.rs +++ b/crates/moonutil/src/git.rs @@ -18,33 +18,103 @@ use std::path::Path; -use colored::Colorize; - -pub fn is_in_git_repo(path: &Path) -> bool { - let output = std::process::Command::new("git") - .args(["rev-parse", "--is-inside-work-tree"]) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .current_dir(path) - .status(); - match output { - Ok(out) => out.success(), - _ => false, +#[derive(Debug, thiserror::Error)] +#[error("git command failed: `{cmd}`")] +pub struct GitCommandError { + cmd: String, + + #[source] + source: GitCommandErrorKind, +} + +#[derive(Debug, thiserror::Error)] +pub enum GitCommandErrorKind { + #[error(transparent)] + IO(#[from] std::io::Error), + + #[error("non-zero exit code: {0}")] + ExitStatus(i32), + + #[error("unknown exit code")] + UnknownExitCode, +} + +pub struct Stdios { + stdin: std::process::Stdio, + stdout: std::process::Stdio, + stderr: std::process::Stdio, +} + +impl Stdios { + pub fn inherit() -> Self { + Self { + stdin: std::process::Stdio::inherit(), + stdout: std::process::Stdio::inherit(), + stderr: std::process::Stdio::inherit(), + } + } + + pub fn npp() -> Self { + Self { + stdin: std::process::Stdio::null(), + stdout: std::process::Stdio::piped(), + stderr: std::process::Stdio::piped(), + } + } +} +pub fn git_command(args: &[&str], stdios: Stdios) -> Result { + std::process::Command::new("git") + .args(args) + .stdin(stdios.stdin) + .stdout(stdios.stdout) + .stderr(stdios.stderr) + .spawn() + .map_err(|e| GitCommandError { + cmd: format!("git {}", args.join(" ")), + source: GitCommandErrorKind::IO(e), + }) +} + +pub fn is_in_git_repo(path: &Path) -> Result { + let args = [ + "-C", + path.to_str().unwrap(), + "rev-parse", + "--is-inside-work-tree", + ]; + let mut output = git_command(&args, Stdios::npp())?; + let status = output.wait(); + match status { + Ok(status) => Ok(status.success()), + Err(e) => Err(GitCommandError { + cmd: format!("git {}", args.join(" ")), + source: GitCommandErrorKind::IO(e), + }), } } -pub fn git_init_repo(path: &Path) { - let git_init = std::process::Command::new("git") - .arg("init") - .current_dir(path) - .status(); - match git_init { - Ok(o) => if o.success() {}, - _ => { - eprintln!( - "{}: git init failed, make sure you have git in PATH", - "Warning".yellow().bold() - ); +pub fn git_init_repo(path: &Path) -> Result<(), GitCommandError> { + let args = ["-C", path.to_str().unwrap(), "init"]; + let mut git_init = git_command(&args, Stdios::inherit())?; + let status = git_init.wait().map_err(|e| GitCommandError { + cmd: format!("git {}", args.join(" ")), + source: GitCommandErrorKind::IO(e), + })?; + if !status.success() { + match status.code() { + Some(code) => { + return Err(GitCommandError { + cmd: format!("git {}", args.join(" ")), + source: GitCommandErrorKind::ExitStatus(code), + }); + } + None => { + return Err(GitCommandError { + cmd: format!("git {}", args.join(" ")), + source: GitCommandErrorKind::UnknownExitCode, + }) + } } } + Ok(()) }