git_iris/services/git_commit.rs
1//! Git commit service
2//!
3//! Focused service for git commit operations. This extracts the commit-specific
4//! functionality from the monolithic `IrisCommitService`.
5
6use anyhow::Result;
7use std::sync::Arc;
8
9use crate::git::{CommitResult, GitRepo};
10use crate::log_debug;
11
12/// Service for performing git commit operations
13///
14/// This service handles:
15/// - Creating commits with optional hook verification
16/// - Pre-commit hook execution
17/// - Remote repository detection
18///
19/// It does NOT handle:
20/// - LLM operations (handled by `IrisAgentService`)
21/// - Context gathering (handled by agents)
22/// - Message generation (handled by agents)
23pub struct GitCommitService {
24 repo: Arc<GitRepo>,
25 verify: bool,
26}
27
28impl GitCommitService {
29 /// Create a new `GitCommitService`
30 ///
31 /// # Arguments
32 /// * `repo` - The git repository to operate on
33 /// * `_use_gitmoji` - Retained for API compatibility; commit messages are
34 /// stored exactly as provided
35 /// * `verify` - Whether to run pre/post-commit hooks
36 #[must_use]
37 pub fn new(repo: Arc<GitRepo>, _use_gitmoji: bool, verify: bool) -> Self {
38 Self { repo, verify }
39 }
40
41 /// Create from an existing `GitRepo` (convenience constructor)
42 #[must_use]
43 pub fn from_repo(repo: GitRepo, use_gitmoji: bool, verify: bool) -> Self {
44 Self::new(Arc::new(repo), use_gitmoji, verify)
45 }
46
47 /// Check if the repository is a remote repository
48 #[must_use]
49 pub fn is_remote(&self) -> bool {
50 self.repo.is_remote()
51 }
52
53 /// Execute the pre-commit hook if verification is enabled
54 ///
55 /// Returns Ok(()) if:
56 /// - verify is false (hooks disabled)
57 /// - repository is remote (hooks don't apply)
58 /// - pre-commit hook succeeds
59 ///
60 /// # Errors
61 ///
62 /// Returns an error when hook verification is enabled and the pre-commit hook fails.
63 pub fn pre_commit(&self) -> Result<()> {
64 if self.is_remote() {
65 log_debug!("Skipping pre-commit hook for remote repository");
66 return Ok(());
67 }
68
69 if self.verify {
70 self.repo.execute_hook("pre-commit")
71 } else {
72 Ok(())
73 }
74 }
75
76 /// Perform a commit with the given message
77 ///
78 /// This method:
79 /// 1. Validates the repository is not remote
80 /// 2. Uses the exact message provided
81 /// 3. Runs pre-commit hook (if verify is enabled)
82 /// 4. Creates the commit
83 /// 5. Runs post-commit hook (if verify is enabled)
84 ///
85 /// # Arguments
86 /// * `message` - The commit message to use
87 ///
88 /// # Returns
89 /// The result of the commit operation
90 ///
91 /// # Errors
92 ///
93 /// Returns an error when the repository is remote, hooks fail, or Git cannot create the commit.
94 pub fn perform_commit(&self, message: &str) -> Result<CommitResult> {
95 if self.is_remote() {
96 return Err(anyhow::anyhow!("Cannot commit to a remote repository"));
97 }
98
99 log_debug!("Performing commit with message: {}", message);
100
101 if !self.verify {
102 log_debug!("Skipping pre-commit hook (verify=false)");
103 return self.repo.commit(message);
104 }
105
106 // Execute pre-commit hook
107 log_debug!("Executing pre-commit hook");
108 if let Err(e) = self.repo.execute_hook("pre-commit") {
109 log_debug!("Pre-commit hook failed: {}", e);
110 return Err(e);
111 }
112 log_debug!("Pre-commit hook executed successfully");
113
114 // Perform the commit
115 match self.repo.commit(message) {
116 Ok(result) => {
117 // Execute post-commit hook (failure doesn't fail the commit)
118 log_debug!("Executing post-commit hook");
119 if let Err(e) = self.repo.execute_hook("post-commit") {
120 log_debug!("Post-commit hook failed: {}", e);
121 }
122 log_debug!("Commit performed successfully");
123 Ok(result)
124 }
125 Err(e) => {
126 log_debug!("Commit failed: {}", e);
127 Err(e)
128 }
129 }
130 }
131
132 /// Amend the previous commit with staged changes and a new message
133 ///
134 /// This method:
135 /// 1. Validates the repository is not remote
136 /// 2. Uses the exact message provided
137 /// 3. Runs pre-commit hook (if verify is enabled)
138 /// 4. Amends the commit (replaces HEAD)
139 /// 5. Runs post-commit hook (if verify is enabled)
140 ///
141 /// # Arguments
142 /// * `message` - The new commit message
143 ///
144 /// # Returns
145 /// The result of the amend operation
146 ///
147 /// # Errors
148 ///
149 /// Returns an error when the repository is remote, hooks fail, or Git cannot amend the commit.
150 pub fn perform_amend(&self, message: &str) -> Result<CommitResult> {
151 if self.is_remote() {
152 return Err(anyhow::anyhow!(
153 "Cannot amend a commit in a remote repository"
154 ));
155 }
156
157 log_debug!("Performing amend with message: {}", message);
158
159 if !self.verify {
160 log_debug!("Skipping pre-commit hook (verify=false)");
161 return self.repo.amend_commit(message);
162 }
163
164 // Execute pre-commit hook
165 log_debug!("Executing pre-commit hook");
166 if let Err(e) = self.repo.execute_hook("pre-commit") {
167 log_debug!("Pre-commit hook failed: {}", e);
168 return Err(e);
169 }
170 log_debug!("Pre-commit hook executed successfully");
171
172 // Perform the amend
173 match self.repo.amend_commit(message) {
174 Ok(result) => {
175 // Execute post-commit hook (failure doesn't fail the amend)
176 log_debug!("Executing post-commit hook");
177 if let Err(e) = self.repo.execute_hook("post-commit") {
178 log_debug!("Post-commit hook failed: {}", e);
179 }
180 log_debug!("Amend performed successfully");
181 Ok(result)
182 }
183 Err(e) => {
184 log_debug!("Amend failed: {}", e);
185 Err(e)
186 }
187 }
188 }
189
190 /// Get the message of the HEAD commit
191 ///
192 /// Useful for amend operations to provide original context
193 ///
194 /// # Errors
195 ///
196 /// Returns an error when the HEAD commit cannot be read.
197 pub fn get_head_commit_message(&self) -> Result<String> {
198 self.repo.get_head_commit_message()
199 }
200
201 /// Get a reference to the underlying repository
202 #[must_use]
203 pub fn repo(&self) -> &GitRepo {
204 &self.repo
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 #[test]
211 fn test_git_commit_service_construction() {
212 // This test just verifies the API compiles correctly
213 // Real tests would need a mock GitRepo
214 }
215}