ricecoder_files/
conflict.rs1use crate::error::FileError;
4use crate::models::{ConflictInfo, ConflictResolution};
5use std::path::Path;
6use tokio::fs;
7
8#[derive(Debug, Clone)]
10pub struct ConflictResolver;
11
12impl ConflictResolver {
13 pub fn new() -> Self {
15 ConflictResolver
16 }
17
18 pub async fn detect_conflict(
29 &self,
30 path: &Path,
31 new_content: &str,
32 ) -> Result<Option<ConflictInfo>, FileError> {
33 if !path.exists() {
34 return Ok(None);
35 }
36
37 let existing_content = fs::read_to_string(path)
38 .await
39 .map_err(|_e| FileError::ConflictDetected(path.to_path_buf()))?;
40
41 Ok(Some(ConflictInfo {
42 path: path.to_path_buf(),
43 existing_content,
44 new_content: new_content.to_string(),
45 }))
46 }
47
48 pub fn resolve(
59 &self,
60 strategy: ConflictResolution,
61 conflict_info: &ConflictInfo,
62 ) -> Result<(), FileError> {
63 match strategy {
64 ConflictResolution::Skip => {
65 Err(FileError::ConflictDetected(conflict_info.path.clone()))
66 }
67 ConflictResolution::Overwrite => {
68 Ok(())
70 }
71 ConflictResolution::Merge => {
72 Ok(())
75 }
76 }
77 }
78
79 pub fn merge_content(existing: &str, new: &str) -> String {
90 if existing == new {
92 return new.to_string();
93 }
94
95 format!(
97 "<<<<<<< EXISTING\n{}\n=======\n{}\n>>>>>>> NEW",
98 existing, new
99 )
100 }
101}
102
103impl Default for ConflictResolver {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[tokio::test]
114 async fn test_detect_conflict_no_file() {
115 let resolver = ConflictResolver::new();
116 let temp_dir = tempfile::tempdir().unwrap();
117 let path = temp_dir.path().join("nonexistent.txt");
118
119 let result = resolver.detect_conflict(&path, "new content").await;
120 assert!(result.is_ok());
121 assert!(result.unwrap().is_none());
122 }
123
124 #[tokio::test]
125 async fn test_detect_conflict_file_exists() {
126 let resolver = ConflictResolver::new();
127 let temp_dir = tempfile::tempdir().unwrap();
128 let path = temp_dir.path().join("existing.txt");
129
130 fs::write(&path, "existing content").await.unwrap();
131
132 let result = resolver.detect_conflict(&path, "new content").await;
133 assert!(result.is_ok());
134
135 let conflict = result.unwrap();
136 assert!(conflict.is_some());
137
138 let conflict_info = conflict.unwrap();
139 assert_eq!(conflict_info.path, path);
140 assert_eq!(conflict_info.existing_content, "existing content");
141 assert_eq!(conflict_info.new_content, "new content");
142 }
143
144 #[test]
145 fn test_resolve_skip_strategy() {
146 let resolver = ConflictResolver::new();
147 let conflict_info = ConflictInfo {
148 path: "test.txt".into(),
149 existing_content: "existing".to_string(),
150 new_content: "new".to_string(),
151 };
152
153 let result = resolver.resolve(ConflictResolution::Skip, &conflict_info);
154 assert!(result.is_err());
155 match result {
156 Err(FileError::ConflictDetected(_)) => (),
157 _ => panic!("Expected ConflictDetected error"),
158 }
159 }
160
161 #[test]
162 fn test_resolve_overwrite_strategy() {
163 let resolver = ConflictResolver::new();
164 let conflict_info = ConflictInfo {
165 path: "test.txt".into(),
166 existing_content: "existing".to_string(),
167 new_content: "new".to_string(),
168 };
169
170 let result = resolver.resolve(ConflictResolution::Overwrite, &conflict_info);
171 assert!(result.is_ok());
172 }
173
174 #[test]
175 fn test_resolve_merge_strategy() {
176 let resolver = ConflictResolver::new();
177 let conflict_info = ConflictInfo {
178 path: "test.txt".into(),
179 existing_content: "existing".to_string(),
180 new_content: "new".to_string(),
181 };
182
183 let result = resolver.resolve(ConflictResolution::Merge, &conflict_info);
184 assert!(result.is_ok());
185 }
186
187 #[test]
188 fn test_merge_content_identical() {
189 let content = "same content";
190 let merged = ConflictResolver::merge_content(content, content);
191 assert_eq!(merged, content);
192 }
193
194 #[test]
195 fn test_merge_content_different() {
196 let existing = "existing content";
197 let new = "new content";
198 let merged = ConflictResolver::merge_content(existing, new);
199
200 assert!(merged.contains("<<<<<<< EXISTING"));
201 assert!(merged.contains("======="));
202 assert!(merged.contains(">>>>>>> NEW"));
203 assert!(merged.contains(existing));
204 assert!(merged.contains(new));
205 }
206}