git_squish/
error.rs

1use git2::{ErrorClass, ErrorCode};
2use std::fmt;
3
4/// Custom error type for git-squish operations
5#[derive(Debug)]
6pub enum SquishError {
7    /// Git operation error with optional enhanced context
8    Git { message: String },
9    /// Other errors
10    Other { message: String },
11}
12
13impl fmt::Display for SquishError {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        match self {
16            SquishError::Git { message } => write!(f, "{message}"),
17            SquishError::Other { message } => write!(f, "{message}"),
18        }
19    }
20}
21
22impl std::error::Error for SquishError {}
23
24impl From<git2::Error> for SquishError {
25    fn from(error: git2::Error) -> Self {
26        // Check if this is a conflict-related error
27        let is_conflict = match (error.class(), error.code()) {
28            (ErrorClass::Merge, ErrorCode::Conflict) => true,
29            (ErrorClass::Merge, ErrorCode::MergeConflict) => true,
30            (ErrorClass::Index, ErrorCode::Unmerged) => true,
31            (ErrorClass::Checkout, ErrorCode::Conflict) => true,
32            _ => {
33                // Also check the error message for conflict-related keywords
34                let msg = error.message().to_lowercase();
35                msg.contains("conflict") || (msg.contains("merge") && msg.contains("failed"))
36            }
37        };
38
39        let message = if is_conflict {
40            "There was a conflict during this squish, please retry using git rebase -i and resolve the conflicts".to_string()
41        } else {
42            error.message().to_string()
43        };
44
45        SquishError::Git { message }
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use git2::Error;
53    use std::error::Error as StdError;
54
55    #[test]
56    fn test_squish_error_display() {
57        let git_error = SquishError::Git {
58            message: "Test git error".to_string(),
59        };
60        assert_eq!(format!("{}", git_error), "Test git error");
61
62        let other_error = SquishError::Other {
63            message: "Test other error".to_string(),
64        };
65        assert_eq!(format!("{}", other_error), "Test other error");
66    }
67
68    #[test]
69    fn test_squish_error_debug() {
70        let error = SquishError::Git {
71            message: "Debug test".to_string(),
72        };
73        let debug_output = format!("{:?}", error);
74        assert!(debug_output.contains("Git"));
75        assert!(debug_output.contains("Debug test"));
76    }
77
78    #[test]
79    fn test_from_git2_conflict_error() {
80        // Create a mock conflict error
81        let git_error = Error::from_str("merge conflict in file.txt");
82        let squish_error = SquishError::from(git_error);
83
84        if let SquishError::Git { message } = squish_error {
85            assert_eq!(
86                message,
87                "There was a conflict during this squish, please retry using git rebase -i and resolve the conflicts"
88            );
89        } else {
90            panic!("Expected Git error variant");
91        }
92    }
93
94    #[test]
95    fn test_from_git2_non_conflict_error() {
96        let git_error = Error::from_str("repository not found");
97        let squish_error = SquishError::from(git_error);
98
99        if let SquishError::Git { message } = squish_error {
100            assert_eq!(message, "repository not found");
101        } else {
102            panic!("Expected Git error variant");
103        }
104    }
105
106    #[test]
107    fn test_from_git2_merge_failed_error() {
108        let git_error = Error::from_str("merge failed due to conflicts");
109        let squish_error = SquishError::from(git_error);
110
111        if let SquishError::Git { message } = squish_error {
112            assert_eq!(
113                message,
114                "There was a conflict during this squish, please retry using git rebase -i and resolve the conflicts"
115            );
116        } else {
117            panic!("Expected Git error variant");
118        }
119    }
120
121    #[test]
122    fn test_conflict_detection_keywords() {
123        let test_cases = vec![
124            ("conflict in file", true),
125            ("CONFLICT in file", true),
126            ("merge failed", true),
127            ("repository not found", false),
128            ("invalid reference", false),
129            ("nothing to merge failed", true), // contains "merge" and "failed"
130        ];
131
132        for (error_msg, should_be_conflict) in test_cases {
133            let git_error = Error::from_str(error_msg);
134            let squish_error = SquishError::from(git_error);
135
136            if let SquishError::Git { message } = squish_error {
137                if should_be_conflict {
138                    assert_eq!(
139                        message,
140                        "There was a conflict during this squish, please retry using git rebase -i and resolve the conflicts",
141                        "Error message '{}' should be detected as conflict",
142                        error_msg
143                    );
144                } else {
145                    assert_eq!(
146                        message, error_msg,
147                        "Error message '{}' should not be detected as conflict",
148                        error_msg
149                    );
150                }
151            } else {
152                panic!("Expected Git error variant");
153            }
154        }
155    }
156
157    #[test]
158    fn test_error_trait_implementation() {
159        let error = SquishError::Other {
160            message: "test error".to_string(),
161        };
162
163        // Test that it implements std::error::Error
164        let _error_ref: &dyn std::error::Error = &error;
165
166        // Test source method (should return None for our simple implementation)
167        assert!(StdError::source(&error).is_none());
168    }
169}