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