Skip to main content

volition_core/tools/
git.rs

1// volition-agent-core/src/tools/git.rs
2
3use super::CommandOutput;
4use crate::utils::truncate_string; // <-- Import the helper
5use anyhow::{Context, Result};
6use std::path::Path;
7use std::process::{Command, Stdio};
8use tracing::{debug, info};
9
10pub async fn execute_git_command(
11    command_name: &str,
12    command_args: &[String],
13    working_dir: &Path,
14) -> Result<CommandOutput> {
15    let full_command_log = format!("git {} {}", command_name, command_args.join(" "));
16    // Truncate command for logging
17    let command_display = truncate_string(&full_command_log, 60);
18    info!(
19        "Executing git command: {} in {:?}",
20        command_display, // <-- Use truncated version
21        working_dir
22    );
23
24    let output = Command::new("git")
25        .current_dir(working_dir)
26        .arg(command_name)
27        .args(command_args)
28        .stdout(Stdio::piped())
29        .stderr(Stdio::piped())
30        .output()
31        .with_context(|| format!("Failed to execute git command: {}", full_command_log))?;
32
33    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
34    let stderr = String::from_utf8_lossy(&output.stderr).to_string();
35    let status = output.status.code().unwrap_or(-1);
36
37    debug!("git {} exit status: {}", full_command_log, status);
38
39    Ok(CommandOutput {
40        status,
41        stdout,
42        stderr,
43    })
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use std::fs;
50    use std::path::PathBuf;
51    use std::process::Command;
52    use tempfile::tempdir;
53    use tokio;
54
55    fn setup_git_repo() -> Result<PathBuf> {
56        let dir = tempdir()?.into_path();
57        Command::new("git").current_dir(&dir).arg("init").output()?;
58        Command::new("git")
59            .current_dir(&dir)
60            .args(&["config", "user.email", "test@example.com"])
61            .output()?;
62        Command::new("git")
63            .current_dir(&dir)
64            .args(&["config", "user.name", "Test User"])
65            .output()?;
66        fs::write(dir.join("README.md"), "Initial commit")?;
67        Command::new("git")
68            .current_dir(&dir)
69            .arg("add")
70            .arg("README.md")
71            .output()?;
72        Command::new("git")
73            .current_dir(&dir)
74            .arg("commit")
75            .arg("-m")
76            .arg("Initial commit")
77            .output()?;
78        Ok(dir)
79    }
80
81    #[tokio::test]
82    async fn test_execute_git_status_clean() {
83        let working_dir = setup_git_repo().expect("Failed to setup git repo");
84        let result = execute_git_command("status", &[], &working_dir).await;
85        assert!(result.is_ok(), "git status failed: {:?}", result.err());
86        let output = result.unwrap();
87        println!("Output: {:?}", output);
88        assert_eq!(output.status, 0);
89        assert!(
90            output
91                .stdout
92                .contains("nothing to commit, working tree clean")
93        );
94    }
95
96    #[tokio::test]
97    async fn test_execute_git_log_initial() {
98        let working_dir = setup_git_repo().expect("Failed to setup git repo");
99        let result = execute_git_command("log", &["-1".to_string()], &working_dir).await;
100        assert!(result.is_ok(), "git log failed: {:?}", result.err());
101        let output = result.unwrap();
102        println!("Output: {:?}", output);
103        assert_eq!(output.status, 0);
104        assert!(output.stdout.contains("Initial commit"));
105    }
106
107    #[tokio::test]
108    async fn test_execute_git_diff_fail() {
109        let working_dir = setup_git_repo().expect("Failed to setup git repo");
110        let result =
111            execute_git_command("diff", &["nonexistentcommit".to_string()], &working_dir).await;
112        assert!(result.is_ok());
113        let output = result.unwrap();
114        println!("Output: {:?}", output);
115        assert_ne!(output.status, 0);
116        assert!(output.stderr.contains("fatal: ambiguous argument"));
117    }
118}