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#[cfg(test)]
51mod tests {
52 use super::super::Tool;
53 use super::*;
54 use tempfile::TempDir;
55
56 #[tokio::test]
57 async fn test_git_tool_schemas() {
58 let tmp = TempDir::new().unwrap();
59 let tools: Vec<(&str, Box<dyn Tool>)> = vec![
61 (
62 "git_status",
63 Box::new(status::GitStatusTool::new(tmp.path().into())),
64 ),
65 (
66 "git_diff",
67 Box::new(diff::GitDiffTool::new(tmp.path().into())),
68 ),
69 (
70 "git_add",
71 Box::new(staging::GitAddTool::new(tmp.path().into())),
72 ),
73 (
74 "git_commit",
75 Box::new(staging::GitCommitTool::new(tmp.path().into())),
76 ),
77 ("git_log", Box::new(log::GitLogTool::new(tmp.path().into()))),
78 (
79 "git_blame",
80 Box::new(log::GitBlameTool::new(tmp.path().into())),
81 ),
82 (
83 "git_branch",
84 Box::new(branch::GitBranchTool::new(tmp.path().into())),
85 ),
86 (
87 "git_checkout",
88 Box::new(branch::GitCheckoutTool::new(tmp.path().into())),
89 ),
90 (
91 "git_stash",
92 Box::new(branch::GitStashTool::new(tmp.path().into())),
93 ),
94 ];
95 for (expected_name, tool) in &tools {
96 assert_eq!(tool.name(), *expected_name, "Tool name mismatch");
97 assert!(
98 !tool.description().is_empty(),
99 "Missing description for {}",
100 expected_name
101 );
102 let schema = tool.parameters_schema();
103 assert!(
104 schema.is_object(),
105 "Schema should be object for {}",
106 expected_name
107 );
108 }
109 }
110}