Skip to main content

ag_git/
error.rs

1/// Typed error returned by git infrastructure operations.
2///
3/// Wraps command execution failures, output parsing issues, and I/O errors so
4/// callers can distinguish error categories without parsing opaque strings.
5#[derive(Debug, thiserror::Error)]
6pub enum GitError {
7    /// A git subprocess exited with a non-zero status.
8    #[error("{command}: {stderr}")]
9    CommandFailed {
10        /// The git command that was executed (e.g. `"git rebase main"`).
11        command: String,
12        /// Human-readable detail extracted from stderr/stdout.
13        stderr: String,
14    },
15
16    /// Git command output could not be parsed into the expected structure.
17    #[error("{0}")]
18    OutputParse(String),
19
20    /// A filesystem or process-spawn operation failed.
21    #[error("{0}")]
22    Io(#[from] std::io::Error),
23
24    /// A `tokio::task::spawn_blocking` join failed.
25    #[error("Join error: {0}")]
26    Join(#[from] tokio::task::JoinError),
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    #[test]
34    fn command_failed_display_includes_command_and_stderr() {
35        // Arrange
36        let error = GitError::CommandFailed {
37            command: "git push origin main".to_string(),
38            stderr: "fatal: could not read Username".to_string(),
39        };
40
41        // Act
42        let display = error.to_string();
43
44        // Assert
45        assert!(matches!(
46            error,
47            GitError::CommandFailed {
48                ref command,
49                ref stderr,
50            } if command == "git push origin main" && stderr == "fatal: could not read Username"
51        ));
52        assert_eq!(
53            display,
54            "git push origin main: fatal: could not read Username"
55        );
56    }
57
58    #[test]
59    fn output_parse_display_shows_message() {
60        // Arrange
61        let error = GitError::OutputParse("unexpected rev-parse output".to_string());
62
63        // Act / Assert
64        assert!(
65            matches!(error, GitError::OutputParse(ref message) if message == "unexpected rev-parse output")
66        );
67        assert_eq!(error.to_string(), "unexpected rev-parse output");
68    }
69
70    #[test]
71    fn io_error_converts_via_from() {
72        // Arrange
73        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
74
75        // Act
76        let error = GitError::from(io_error);
77
78        // Assert
79        assert!(matches!(error, GitError::Io(_)));
80        assert!(error.to_string().contains("file missing"));
81    }
82}