auto_gitmoji/
commit.rs

1use anyhow::{Context, Result};
2use std::process::Command;
3
4/// Git command execution errors
5#[derive(Debug, thiserror::Error)]
6pub enum GitError {
7    #[error("Git command failed: {0}")]
8    CommandFailed(String),
9    #[error("Not in a Git repository")]
10    NotInRepository,
11    #[error("Git binary not found")]
12    GitNotFound,
13}
14
15/// Git commit functionality
16pub struct GitCommit;
17
18impl GitCommit {
19    /// Check if we're in a Git repository
20    pub fn is_git_repository() -> Result<bool> {
21        let output = Command::new("git")
22            .args(["rev-parse", "--is-inside-work-tree"])
23            .output()
24            .context("Failed to execute git command")?;
25
26        Ok(output.status.success())
27    }
28
29    /// Format a commit message with emoji
30    pub fn format_message(emoji_code: &str, message: &str) -> String {
31        format!("{emoji_code} {message}")
32    }
33
34    /// Execute git commit with the formatted message
35    pub fn commit(message: &str, dry_run: bool) -> Result<String> {
36        // Verify we're in a Git repository
37        if !Self::is_git_repository()? {
38            return Err(GitError::NotInRepository.into());
39        }
40
41        if dry_run {
42            return Ok(format!(
43                "DRY RUN: Would execute: git commit -m \"{message}\"",
44            ));
45        }
46
47        let output = Command::new("git")
48            .args(["commit", "-m", message])
49            .output()
50            .context("Failed to execute git commit")?;
51
52        if output.status.success() {
53            let stdout = String::from_utf8_lossy(&output.stdout);
54            Ok(format!("Git commit successful:\n{stdout}",))
55        } else {
56            let stderr = String::from_utf8_lossy(&output.stderr);
57            Err(GitError::CommandFailed(stderr.to_string()).into())
58        }
59    }
60
61    /// Get git status to show staged changes
62    pub fn status() -> Result<String> {
63        let output = Command::new("git")
64            .args(["status", "--porcelain"])
65            .output()
66            .context("Failed to execute git status")?;
67
68        if output.status.success() {
69            let stdout = String::from_utf8_lossy(&output.stdout);
70            Ok(stdout.to_string())
71        } else {
72            let stderr = String::from_utf8_lossy(&output.stderr);
73            Err(GitError::CommandFailed(stderr.to_string()).into())
74        }
75    }
76
77    /// Check if there are staged changes to commit
78    pub fn has_staged_changes() -> Result<bool> {
79        let status = Self::status()?;
80        // Look for staged changes (lines starting with A, M, D, R, C in first column)
81        Ok(status.lines().any(|line| {
82            if line.len() >= 2 {
83                matches!(line.chars().next(), Some('A' | 'M' | 'D' | 'R' | 'C'))
84            } else {
85                false
86            }
87        }))
88    }
89}