ararajuba_tools_coding/git/
push.rs1use ararajuba_core::tools::tool::{tool, ToolDef};
4use git2::Repository;
5use serde_json::json;
6
7pub fn git_push_tool() -> ToolDef {
13 tool("git_push")
14 .description("Push to a remote. Requires approval (especially for force push).")
15 .input_schema(json!({
16 "type": "object",
17 "properties": {
18 "remote": { "type": "string", "description": "Remote name (default: origin)" },
19 "branch": { "type": "string", "description": "Branch to push (default: current)" },
20 "force": { "type": "boolean", "description": "Force push (default false)" }
21 }
22 }))
23 .execute(|input| async move {
24 let remote_name = input["remote"].as_str().unwrap_or("origin");
25 let force = input["force"].as_bool().unwrap_or(false);
26
27 let repo = Repository::discover(".")
28 .map_err(|e| format!("failed to open repository: {e}"))?;
29
30 let branch_name = if let Some(b) = input["branch"].as_str() {
31 b.to_string()
32 } else {
33 repo.head()
34 .ok()
35 .and_then(|h| h.shorthand().map(String::from))
36 .ok_or_else(|| "cannot determine current branch".to_string())?
37 };
38
39 let mut remote = repo
40 .find_remote(remote_name)
41 .map_err(|e| format!("remote not found: {e}"))?;
42
43 let refspec = if force {
44 format!("+refs/heads/{branch_name}:refs/heads/{branch_name}")
45 } else {
46 format!("refs/heads/{branch_name}:refs/heads/{branch_name}")
47 };
48
49 remote
50 .push(&[&refspec], None)
51 .map_err(|e| format!("push failed: {e}"))?;
52
53 Ok(json!({ "ok": true }))
54 })
55 .needs_approval(|_input| true)
56 .build()
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn tool_metadata() {
65 let t = git_push_tool();
66 assert_eq!(t.name, "git_push");
67 assert!(t.execute.is_some());
68 assert!(t.needs_approval.is_some());
69 }
70}