use std::path::{Path, PathBuf};
use std::process::Command;
use super::{Repo, RepoError};
impl Repo {
pub async fn commit(&self, paths: &[&Path], message: &str) -> Result<String, RepoError> {
if paths.is_empty() {
return Err(RepoError::CommitFailed {
cause: "no paths supplied — commit requires at least one path to stage".into(),
});
}
let workdir = self.path().to_path_buf();
let paths_owned: Vec<PathBuf> = paths.iter().map(|p| p.to_path_buf()).collect();
let message = message.to_string();
tokio::task::spawn_blocking(move || run_commit(&workdir, &paths_owned, &message))
.await
.map_err(|join| RepoError::CommitFailed {
cause: format!("spawn_blocking join: {join}"),
})?
}
}
fn run_commit(workdir: &Path, paths: &[PathBuf], message: &str) -> Result<String, RepoError> {
let mut add = Command::new("git");
add.arg("add").arg("--").current_dir(workdir);
for p in paths {
add.arg(p);
}
let add_out = add
.output()
.map_err(|e| RepoError::CommitFailed { cause: format!("spawn `git add`: {e}") })?;
if !add_out.status.success() {
return Err(RepoError::CommitFailed {
cause: format!("git add: {}", String::from_utf8_lossy(&add_out.stderr).trim()),
});
}
let commit_out = Command::new("git")
.arg("commit")
.arg("-m")
.arg(message)
.current_dir(workdir)
.output()
.map_err(|e| RepoError::CommitFailed { cause: format!("spawn `git commit`: {e}") })?;
if !commit_out.status.success() {
return Err(RepoError::CommitFailed {
cause: format!("git commit: {}", String::from_utf8_lossy(&commit_out.stderr).trim()),
});
}
let rev_out =
Command::new("git").arg("rev-parse").arg("HEAD").current_dir(workdir).output().map_err(
|e| RepoError::CommitFailed { cause: format!("spawn `git rev-parse`: {e}") },
)?;
if !rev_out.status.success() {
return Err(RepoError::CommitFailed {
cause: format!("git rev-parse: {}", String::from_utf8_lossy(&rev_out.stderr).trim()),
});
}
let oid = String::from_utf8(rev_out.stdout)
.map_err(|e| RepoError::CommitFailed { cause: format!("oid utf-8: {e}") })?
.trim()
.to_string();
Ok(oid)
}