volition_core/tools/
git.rs1use super::CommandOutput;
4use crate::utils::truncate_string; use 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 let command_display = truncate_string(&full_command_log, 60);
18 info!(
19 "Executing git command: {} in {:?}",
20 command_display, 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}