git_iris/mcp/tools/
commit.rs1use crate::commit::service::IrisCommitService;
6use crate::commit::types::format_commit_message;
7use crate::config::Config as GitIrisConfig;
8use crate::git::GitRepo;
9use crate::log_debug;
10use crate::mcp::tools::utils::{
11 GitIrisTool, create_text_result, resolve_git_repo, validate_repository_parameter,
12};
13
14use rmcp::handler::server::tool::cached_schema_for_type;
15use rmcp::model::{CallToolResult, Tool};
16use rmcp::schemars;
17
18use serde::{Deserialize, Serialize};
19use std::borrow::Cow;
20use std::sync::Arc;
21
22#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
24pub struct CommitTool {
25 #[serde(default)]
27 pub auto_commit: bool,
28
29 #[serde(default)]
31 pub use_gitmoji: bool,
32
33 #[serde(default)]
35 pub no_verify: bool,
36
37 #[serde(default)]
39 pub preset: String,
40
41 #[serde(default)]
43 pub custom_instructions: String,
44
45 pub repository: String,
47}
48
49impl CommitTool {
50 pub fn get_tool_definition() -> Tool {
52 Tool {
53 name: Cow::Borrowed("git_iris_commit"),
54 description: Cow::Borrowed("Generate commit messages and perform Git commits"),
55 input_schema: cached_schema_for_type::<Self>(),
56 }
57 }
58}
59
60#[async_trait::async_trait]
61impl GitIrisTool for CommitTool {
62 async fn execute(
64 &self,
65 git_repo: Arc<GitRepo>,
66 config: GitIrisConfig,
67 ) -> Result<CallToolResult, anyhow::Error> {
68 log_debug!("Processing commit request with: {:?}", self);
69
70 validate_repository_parameter(&self.repository)?;
72 let git_repo = resolve_git_repo(Some(self.repository.as_str()), git_repo)?;
73 log_debug!("Using repository: {}", git_repo.repo_path().display());
74
75 if self.auto_commit && git_repo.is_remote() {
77 return Err(anyhow::anyhow!("Cannot auto-commit to a remote repository"));
78 }
79
80 let provider_name = &config.default_provider;
82 let repo_path = git_repo.repo_path().clone();
83 let verify = !self.no_verify;
84
85 let service = IrisCommitService::new(
87 config.clone(),
88 &repo_path,
89 provider_name,
90 self.use_gitmoji,
91 verify,
92 GitRepo::new(&repo_path)?,
93 )?;
94
95 let git_info = service.get_git_info().await?;
97 if git_info.staged_files.is_empty() {
98 return Err(anyhow::anyhow!(
99 "No staged changes. Please stage your changes before generating a commit message."
100 ));
101 }
102
103 if let Err(e) = service.pre_commit() {
105 return Err(anyhow::anyhow!("Pre-commit failed: {}", e));
106 }
107
108 let preset = if self.preset.is_empty() {
110 "default"
111 } else {
112 &self.preset
113 };
114
115 let message = service
116 .generate_message(preset, &self.custom_instructions)
117 .await?;
118 let formatted_message = format_commit_message(&message);
119
120 if self.auto_commit {
122 match service.perform_commit(&formatted_message) {
123 Ok(result) => {
124 let result_text = format!(
126 "Commit successful! [{}]\n\n{}\n\n{} file{} changed, {} insertion{}(+), {} deletion{}(-)",
127 result.commit_hash,
128 formatted_message,
129 result.files_changed,
130 if result.files_changed == 1 { "" } else { "s" },
131 result.insertions,
132 if result.insertions == 1 { "" } else { "s" },
133 result.deletions,
134 if result.deletions == 1 { "" } else { "s" }
135 );
136
137 return Ok(create_text_result(result_text));
138 }
139 Err(e) => {
140 return Err(anyhow::anyhow!("Failed to commit: {}", e));
141 }
142 }
143 }
144
145 Ok(create_text_result(formatted_message))
147 }
148}