ararajuba_tools_coding/git/
diff.rs1use ararajuba_core::tools::tool::{tool, ToolDef};
4use git2::{DiffOptions, Repository};
5use serde_json::json;
6
7pub fn git_diff_tool() -> ToolDef {
13 tool("git_diff")
14 .description("Show git diff. Use staged=true for index vs HEAD.")
15 .input_schema(json!({
16 "type": "object",
17 "properties": {
18 "path": { "type": "string", "description": "Repository path (default: current dir)" },
19 "staged": { "type": "boolean", "description": "Diff staged changes vs HEAD (default false)" },
20 "file": { "type": "string", "description": "Diff a single file" }
21 }
22 }))
23 .execute(|input| async move {
24 let path = input["path"].as_str().unwrap_or(".");
25 let staged = input["staged"].as_bool().unwrap_or(false);
26 let file_filter = input["file"].as_str();
27
28 let repo = Repository::discover(path)
29 .map_err(|e| format!("failed to open repository: {e}"))?;
30
31 let mut diff_opts = DiffOptions::new();
32 if let Some(f) = file_filter {
33 diff_opts.pathspec(f);
34 }
35
36 let diff = if staged {
37 let head_tree = repo
38 .head()
39 .and_then(|h| h.peel_to_tree())
40 .map_err(|e| format!("failed to get HEAD tree: {e}"))?;
41 repo.diff_tree_to_index(Some(&head_tree), None, Some(&mut diff_opts))
42 } else {
43 repo.diff_index_to_workdir(None, Some(&mut diff_opts))
44 }
45 .map_err(|e| format!("failed to compute diff: {e}"))?;
46
47 let mut diff_text = String::new();
48 diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
49 let origin = line.origin();
50 if origin == '+' || origin == '-' || origin == ' ' {
51 diff_text.push(origin);
52 }
53 diff_text.push_str(
54 std::str::from_utf8(line.content()).unwrap_or(""),
55 );
56 true
57 })
58 .map_err(|e| format!("failed to format diff: {e}"))?;
59
60 Ok(json!({ "diff": diff_text }))
61 })
62 .build()
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn tool_metadata() {
71 let t = git_diff_tool();
72 assert_eq!(t.name, "git_diff");
73 assert!(t.execute.is_some());
74 assert!(t.needs_approval.is_none());
75 }
76}