Skip to main content

soma_codemode/git/
command.rs

1use std::path::PathBuf;
2
3use tokio::process::Command;
4
5use crate::ToolError;
6
7use super::output::cap_output;
8use super::safety::safe_git_env;
9
10#[derive(Debug, Clone)]
11pub struct GitCommand {
12    cwd: PathBuf,
13    args: Vec<String>,
14}
15
16impl GitCommand {
17    pub fn new(cwd: impl Into<PathBuf>, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
18        Self {
19            cwd: cwd.into(),
20            args: args.into_iter().map(Into::into).collect(),
21        }
22    }
23
24    pub async fn run(&self) -> Result<String, ToolError> {
25        let mut command = Command::new("git");
26        command.current_dir(&self.cwd).args(&self.args).env_clear();
27        for (key, value) in safe_git_env() {
28            command.env(key, value);
29        }
30        let output = command
31            .output()
32            .await
33            .map_err(|err| ToolError::internal_message(format!("git failed: {err}")))?;
34        if !output.status.success() {
35            let stderr = cap_output(&output.stderr, 8 * 1024);
36            return Err(ToolError::Sdk {
37                sdk_kind: "upstream_error".to_string(),
38                message: if stderr.trim().is_empty() {
39                    format!("git exited with status {}", output.status)
40                } else {
41                    format!("git exited with status {}: {stderr}", output.status)
42                },
43            });
44        }
45        Ok(cap_output(&output.stdout, 64 * 1024))
46    }
47
48    pub fn args(&self) -> &[String] {
49        &self.args
50    }
51}