Skip to main content

pawan/tools/git/
mod.rs

1//! Git operation tools
2//!
3//! Tools for git operations: status, diff, add, commit, log, blame, branch.
4
5use 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
22/// Run a git command in a workspace directory
23pub(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        // Verify all git tools have correct names and non-empty schemas
61        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}