Skip to main content

ararajuba_tools_coding/git/
push.rs

1//! `git_push` tool — push to a remote. **Requires approval.**
2
3use ararajuba_core::tools::tool::{tool, ToolDef};
4use git2::Repository;
5use serde_json::json;
6
7/// Create the `git_push` tool.
8///
9/// Pushes the current branch (or specified branch) to a remote.
10/// Has `needs_approval` set — force pushes always require approval,
11/// normal pushes also require approval by default (high-risk remote op).
12pub 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}