1use std::path::PathBuf;
6use std::process::Stdio;
7use tokio::io::AsyncReadExt;
8use tokio::process::Command;
9
10pub mod branch;
11pub mod diff;
12pub mod log;
13pub mod staging;
14pub mod status;
15
16pub use branch::{GitBranchTool, GitCheckoutTool, GitStashTool};
17pub use diff::GitDiffTool;
18pub use log::{GitBlameTool, GitLogTool};
19pub use staging::{GitAddTool, GitCommitTool};
20pub use status::GitStatusTool;
21
22pub(crate) async fn run_git(
24 workspace: &PathBuf,
25 args: &[&str],
26) -> crate::Result<(bool, String, String)> {
27 let mut cmd = Command::new("git");
28 cmd.args(args)
29 .current_dir(workspace)
30 .stdout(Stdio::piped())
31 .stderr(Stdio::piped())
32 .stdin(Stdio::null());
33
34 let mut child = cmd.spawn().map_err(crate::PawanError::Io)?;
35
36 let mut stdout = String::new();
37 let mut stderr = String::new();
38
39 if let Some(mut handle) = child.stdout.take() {
40 handle.read_to_string(&mut stdout).await.ok();
41 }
42 if let Some(mut handle) = child.stderr.take() {
43 handle.read_to_string(&mut stderr).await.ok();
44 }
45
46 let status = child.wait().await.map_err(crate::PawanError::Io)?;
47 Ok((status.success(), stdout, stderr))
48}
49
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use super::super::Tool;
55 use tempfile::TempDir;
56
57 #[tokio::test]
58 async fn test_git_tool_schemas() {
59 let tmp = TempDir::new().unwrap();
60 let tools: Vec<(&str, Box<dyn Tool>)> = vec![
62 ("git_status", Box::new(status::GitStatusTool::new(tmp.path().into()))),
63 ("git_diff", Box::new(diff::GitDiffTool::new(tmp.path().into()))),
64 ("git_add", Box::new(staging::GitAddTool::new(tmp.path().into()))),
65 ("git_commit", Box::new(staging::GitCommitTool::new(tmp.path().into()))),
66 ("git_log", Box::new(log::GitLogTool::new(tmp.path().into()))),
67 ("git_blame", Box::new(log::GitBlameTool::new(tmp.path().into()))),
68 ("git_branch", Box::new(branch::GitBranchTool::new(tmp.path().into()))),
69 ("git_checkout", Box::new(branch::GitCheckoutTool::new(tmp.path().into()))),
70 ("git_stash", Box::new(branch::GitStashTool::new(tmp.path().into()))),
71 ];
72 for (expected_name, tool) in &tools {
73 assert_eq!(tool.name(), *expected_name, "Tool name mismatch");
74 assert!(!tool.description().is_empty(), "Missing description for {}", expected_name);
75 let schema = tool.parameters_schema();
76 assert!(schema.is_object(), "Schema should be object for {}", expected_name);
77 }
78 }
79}