use std::ffi::OsStr; use std::ffi::OsString; use std::path::Path; use std::path::PathBuf; use std::process::Command; use crate::GitToolingError; pub(crate) fn ensure_git_repository(path: &Path) -> Result<(), GitToolingError> { match run_git_for_stdout( path, vec![ OsString::from("rev-parse"), OsString::from("--is-inside-work-tree"), ], /*env*/ None, ) { Ok(output) if output.trim() == "true" => Ok(()), Ok(_) => Err(GitToolingError::NotAGitRepository { path: path.to_path_buf(), }), Err(GitToolingError::GitCommand { status, .. }) if status.code() == Some(128) => { Err(GitToolingError::NotAGitRepository { path: path.to_path_buf(), }) } Err(err) => Err(err), } } pub(crate) fn resolve_head(path: &Path) -> Result, GitToolingError> { match run_git_for_stdout( path, vec![ OsString::from("rev-parse"), OsString::from("--verify"), OsString::from("HEAD"), ], /*env*/ None, ) { Ok(sha) => Ok(Some(sha)), Err(GitToolingError::GitCommand { status, .. }) if status.code() == Some(128) => Ok(None), Err(other) => Err(other), } } pub(crate) fn resolve_repository_root(path: &Path) -> Result { let root = run_git_for_stdout( path, vec![ OsString::from("rev-parse"), OsString::from("--show-toplevel"), ], /*env*/ None, )?; Ok(PathBuf::from(root)) } pub(crate) fn run_git_for_status( dir: &Path, args: I, env: Option<&[(OsString, OsString)]>, ) -> Result<(), GitToolingError> where I: IntoIterator, S: AsRef, { run_git(dir, args, env)?; Ok(()) } pub(crate) fn run_git_for_stdout( dir: &Path, args: I, env: Option<&[(OsString, OsString)]>, ) -> Result where I: IntoIterator, S: AsRef, { let run = run_git(dir, args, env)?; String::from_utf8(run.output.stdout) .map(|value| value.trim().to_string()) .map_err(|source| GitToolingError::GitOutputUtf8 { command: run.command, source, }) } fn run_git( dir: &Path, args: I, env: Option<&[(OsString, OsString)]>, ) -> Result where I: IntoIterator, S: AsRef, { let iterator = args.into_iter(); let (lower, upper) = iterator.size_hint(); let mut args_vec = Vec::with_capacity(upper.unwrap_or(lower)); for arg in iterator { args_vec.push(OsString::from(arg.as_ref())); } let command_string = build_command_string(&args_vec); let mut command = Command::new("git"); command.current_dir(dir); if let Some(envs) = env { for (key, value) in envs { command.env(key, value); } } command.args(&args_vec); let output = command.output()?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); return Err(GitToolingError::GitCommand { command: command_string, status: output.status, stderr, }); } Ok(GitRun { command: command_string, output, }) } fn build_command_string(args: &[OsString]) -> String { if args.is_empty() { return "git".to_string(); } let joined = args .iter() .map(|arg| arg.to_string_lossy().into_owned()) .collect::>() .join(" "); format!("git {joined}") } struct GitRun { command: String, output: std::process::Output, }