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