1use crate::patch::types::Patch;
4use crate::patch::types::PatchId;
5use crate::patch::types::TouchSet;
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub enum ConflictClass {
18 AutoResolvable,
20 DriverResolvable,
22 Genuine,
24 Structural,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
33pub struct Conflict {
34 pub patch_a_id: PatchId,
36 pub patch_b_id: PatchId,
38 pub conflict_addresses: Vec<String>,
40}
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
48pub struct ConflictNode {
49 pub patch_a_id: PatchId,
51 pub patch_b_id: PatchId,
53 pub base_patch_id: PatchId,
55 pub touch_set: TouchSet,
57 pub description: String,
59 pub status: ConflictStatus,
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
65pub enum ConflictStatus {
66 Unresolved,
68 ResolvedA,
70 ResolvedB,
72 ResolvedManual,
74}
75
76impl Conflict {
77 pub fn new(patch_a_id: PatchId, patch_b_id: PatchId, conflict_addresses: Vec<String>) -> Self {
79 let mut sorted = conflict_addresses.clone();
80 sorted.sort();
81 Self {
82 patch_a_id,
83 patch_b_id,
84 conflict_addresses: sorted,
85 }
86 }
87
88 pub fn classify(&self, patch_a: Option<&Patch>, patch_b: Option<&Patch>) -> ConflictClass {
92 match (patch_a, patch_b) {
93 (Some(pa), Some(pb)) => {
94 if pa.payload == pb.payload && pa.operation_type == pb.operation_type {
95 ConflictClass::AutoResolvable
96 } else if pa.operation_type == pb.operation_type {
97 let a_sub: Vec<String> = self
98 .conflict_addresses
99 .iter()
100 .filter(|a| pa.touch_set.contains(a))
101 .cloned()
102 .collect();
103 let b_sub: Vec<String> = self
104 .conflict_addresses
105 .iter()
106 .filter(|a| pb.touch_set.contains(a))
107 .cloned()
108 .collect();
109 if a_sub != b_sub {
110 ConflictClass::DriverResolvable
111 } else {
112 ConflictClass::Genuine
113 }
114 } else {
115 ConflictClass::Structural
116 }
117 }
118 _ => ConflictClass::Genuine,
119 }
120 }
121}
122
123impl ConflictNode {
124 pub fn new(
126 patch_a_id: PatchId,
127 patch_b_id: PatchId,
128 base_patch_id: PatchId,
129 touch_set: TouchSet,
130 description: String,
131 ) -> Self {
132 Self {
133 patch_a_id,
134 patch_b_id,
135 base_patch_id,
136 touch_set,
137 description,
138 status: ConflictStatus::Unresolved,
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use suture_common::Hash;
147
148 fn test_hash(s: &str) -> PatchId {
149 Hash::from_data(s.as_bytes())
150 }
151
152 #[test]
153 fn test_conflict_creation() {
154 let c = Conflict::new(
155 test_hash("patch_a"),
156 test_hash("patch_b"),
157 vec!["A1".to_string(), "B1".to_string()],
158 );
159 assert_eq!(c.conflict_addresses.len(), 2);
160 }
161
162 #[test]
163 fn test_conflict_node_creation() {
164 let node = ConflictNode::new(
165 test_hash("patch_a"),
166 test_hash("patch_b"),
167 test_hash("base"),
168 TouchSet::from_addrs(["A1", "B1"]),
169 "Both edited A1".to_string(),
170 );
171 assert_eq!(node.status, ConflictStatus::Unresolved);
172 assert_eq!(node.touch_set.len(), 2);
173 }
174
175 #[test]
176 fn test_classify_auto_resolvable() {
177 let root = Hash::from_data(b"root");
178 let pa = Patch::new(
179 crate::patch::types::OperationType::Modify,
180 TouchSet::from_addrs(["f1"]),
181 Some("f1".to_string()),
182 b"same content".to_vec(),
183 vec![root],
184 "alice".to_string(),
185 "edit".to_string(),
186 );
187 let pb = Patch::new(
188 crate::patch::types::OperationType::Modify,
189 TouchSet::from_addrs(["f1"]),
190 Some("f1".to_string()),
191 b"same content".to_vec(),
192 vec![root],
193 "bob".to_string(),
194 "edit".to_string(),
195 );
196
197 let conflict = Conflict::new(pa.id, pb.id, vec!["f1".to_string()]);
198 assert_eq!(
199 conflict.classify(Some(&pa), Some(&pb)),
200 ConflictClass::AutoResolvable
201 );
202 }
203
204 #[test]
205 fn test_classify_genuine() {
206 let root = Hash::from_data(b"root");
207 let pa = Patch::new(
208 crate::patch::types::OperationType::Modify,
209 TouchSet::from_addrs(["f1"]),
210 Some("f1".to_string()),
211 b"version A".to_vec(),
212 vec![root],
213 "alice".to_string(),
214 "edit A".to_string(),
215 );
216 let pb = Patch::new(
217 crate::patch::types::OperationType::Modify,
218 TouchSet::from_addrs(["f1"]),
219 Some("f1".to_string()),
220 b"version B".to_vec(),
221 vec![root],
222 "bob".to_string(),
223 "edit B".to_string(),
224 );
225
226 let conflict = Conflict::new(pa.id, pb.id, vec!["f1".to_string()]);
227 assert_eq!(
228 conflict.classify(Some(&pa), Some(&pb)),
229 ConflictClass::Genuine
230 );
231 }
232
233 #[test]
234 fn test_classify_structural() {
235 let root = Hash::from_data(b"root");
236 let pa = Patch::new(
237 crate::patch::types::OperationType::Modify,
238 TouchSet::from_addrs(["f1"]),
239 Some("f1".to_string()),
240 b"modified content".to_vec(),
241 vec![root],
242 "alice".to_string(),
243 "modify".to_string(),
244 );
245 let pb = Patch::new(
246 crate::patch::types::OperationType::Delete,
247 TouchSet::from_addrs(["f1"]),
248 Some("f1".to_string()),
249 vec![],
250 vec![root],
251 "bob".to_string(),
252 "delete".to_string(),
253 );
254
255 let conflict = Conflict::new(pa.id, pb.id, vec!["f1".to_string()]);
256 assert_eq!(
257 conflict.classify(Some(&pa), Some(&pb)),
258 ConflictClass::Structural
259 );
260 }
261
262 #[test]
263 fn test_classify_driver_resolvable() {
264 let root = Hash::from_data(b"root");
265 let pa = Patch::new(
266 crate::patch::types::OperationType::Modify,
267 TouchSet::from_addrs(["f1.key_a"]),
268 Some("f1".to_string()),
269 b"val_a".to_vec(),
270 vec![root],
271 "alice".to_string(),
272 "edit key_a".to_string(),
273 );
274 let pb = Patch::new(
275 crate::patch::types::OperationType::Modify,
276 TouchSet::from_addrs(["f1.key_b"]),
277 Some("f1".to_string()),
278 b"val_b".to_vec(),
279 vec![root],
280 "bob".to_string(),
281 "edit key_b".to_string(),
282 );
283
284 let conflict = Conflict::new(
285 pa.id,
286 pb.id,
287 vec!["f1.key_a".to_string(), "f1.key_b".to_string()],
288 );
289 assert_eq!(
290 conflict.classify(Some(&pa), Some(&pb)),
291 ConflictClass::DriverResolvable
292 );
293 }
294
295 #[test]
296 fn test_classify_missing_patches() {
297 let conflict = Conflict::new(test_hash("a"), test_hash("b"), vec!["f1".to_string()]);
298 assert_eq!(conflict.classify(None, None), ConflictClass::Genuine);
299 assert_eq!(conflict.classify(None, None), ConflictClass::Genuine);
300 }
301}