use crate::patch::types::Patch;
use crate::patch::types::PatchId;
use crate::patch::types::TouchSet;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConflictClass {
AutoResolvable,
DriverResolvable,
Genuine,
Structural,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Conflict {
pub patch_a_id: PatchId,
pub patch_b_id: PatchId,
pub conflict_addresses: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConflictNode {
pub patch_a_id: PatchId,
pub patch_b_id: PatchId,
pub base_patch_id: PatchId,
pub touch_set: TouchSet,
pub description: String,
pub status: ConflictStatus,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConflictStatus {
Unresolved,
ResolvedA,
ResolvedB,
ResolvedManual,
}
impl Conflict {
pub fn new(patch_a_id: PatchId, patch_b_id: PatchId, conflict_addresses: Vec<String>) -> Self {
let mut sorted = conflict_addresses.clone();
sorted.sort();
Self {
patch_a_id,
patch_b_id,
conflict_addresses: sorted,
}
}
pub fn classify(&self, patch_a: Option<&Patch>, patch_b: Option<&Patch>) -> ConflictClass {
match (patch_a, patch_b) {
(Some(pa), Some(pb)) => {
if pa.payload == pb.payload && pa.operation_type == pb.operation_type {
ConflictClass::AutoResolvable
} else if pa.operation_type == pb.operation_type {
let a_sub: Vec<String> = self
.conflict_addresses
.iter()
.filter(|a| pa.touch_set.contains(a))
.cloned()
.collect();
let b_sub: Vec<String> = self
.conflict_addresses
.iter()
.filter(|a| pb.touch_set.contains(a))
.cloned()
.collect();
if a_sub != b_sub {
ConflictClass::DriverResolvable
} else {
ConflictClass::Genuine
}
} else {
ConflictClass::Structural
}
}
_ => ConflictClass::Genuine,
}
}
}
impl ConflictNode {
pub fn new(
patch_a_id: PatchId,
patch_b_id: PatchId,
base_patch_id: PatchId,
touch_set: TouchSet,
description: String,
) -> Self {
Self {
patch_a_id,
patch_b_id,
base_patch_id,
touch_set,
description,
status: ConflictStatus::Unresolved,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use suture_common::Hash;
fn test_hash(s: &str) -> PatchId {
Hash::from_data(s.as_bytes())
}
#[test]
fn test_conflict_creation() {
let c = Conflict::new(
test_hash("patch_a"),
test_hash("patch_b"),
vec!["A1".to_string(), "B1".to_string()],
);
assert_eq!(c.conflict_addresses.len(), 2);
}
#[test]
fn test_conflict_node_creation() {
let node = ConflictNode::new(
test_hash("patch_a"),
test_hash("patch_b"),
test_hash("base"),
TouchSet::from_addrs(["A1", "B1"]),
"Both edited A1".to_string(),
);
assert_eq!(node.status, ConflictStatus::Unresolved);
assert_eq!(node.touch_set.len(), 2);
}
#[test]
fn test_classify_auto_resolvable() {
let root = Hash::from_data(b"root");
let pa = Patch::new(
crate::patch::types::OperationType::Modify,
TouchSet::from_addrs(["f1"]),
Some("f1".to_string()),
b"same content".to_vec(),
vec![root],
"alice".to_string(),
"edit".to_string(),
);
let pb = Patch::new(
crate::patch::types::OperationType::Modify,
TouchSet::from_addrs(["f1"]),
Some("f1".to_string()),
b"same content".to_vec(),
vec![root],
"bob".to_string(),
"edit".to_string(),
);
let conflict = Conflict::new(pa.id, pb.id, vec!["f1".to_string()]);
assert_eq!(
conflict.classify(Some(&pa), Some(&pb)),
ConflictClass::AutoResolvable
);
}
#[test]
fn test_classify_genuine() {
let root = Hash::from_data(b"root");
let pa = Patch::new(
crate::patch::types::OperationType::Modify,
TouchSet::from_addrs(["f1"]),
Some("f1".to_string()),
b"version A".to_vec(),
vec![root],
"alice".to_string(),
"edit A".to_string(),
);
let pb = Patch::new(
crate::patch::types::OperationType::Modify,
TouchSet::from_addrs(["f1"]),
Some("f1".to_string()),
b"version B".to_vec(),
vec![root],
"bob".to_string(),
"edit B".to_string(),
);
let conflict = Conflict::new(pa.id, pb.id, vec!["f1".to_string()]);
assert_eq!(
conflict.classify(Some(&pa), Some(&pb)),
ConflictClass::Genuine
);
}
#[test]
fn test_classify_structural() {
let root = Hash::from_data(b"root");
let pa = Patch::new(
crate::patch::types::OperationType::Modify,
TouchSet::from_addrs(["f1"]),
Some("f1".to_string()),
b"modified content".to_vec(),
vec![root],
"alice".to_string(),
"modify".to_string(),
);
let pb = Patch::new(
crate::patch::types::OperationType::Delete,
TouchSet::from_addrs(["f1"]),
Some("f1".to_string()),
vec![],
vec![root],
"bob".to_string(),
"delete".to_string(),
);
let conflict = Conflict::new(pa.id, pb.id, vec!["f1".to_string()]);
assert_eq!(
conflict.classify(Some(&pa), Some(&pb)),
ConflictClass::Structural
);
}
#[test]
fn test_classify_driver_resolvable() {
let root = Hash::from_data(b"root");
let pa = Patch::new(
crate::patch::types::OperationType::Modify,
TouchSet::from_addrs(["f1.key_a"]),
Some("f1".to_string()),
b"val_a".to_vec(),
vec![root],
"alice".to_string(),
"edit key_a".to_string(),
);
let pb = Patch::new(
crate::patch::types::OperationType::Modify,
TouchSet::from_addrs(["f1.key_b"]),
Some("f1".to_string()),
b"val_b".to_vec(),
vec![root],
"bob".to_string(),
"edit key_b".to_string(),
);
let conflict = Conflict::new(
pa.id,
pb.id,
vec!["f1.key_a".to_string(), "f1.key_b".to_string()],
);
assert_eq!(
conflict.classify(Some(&pa), Some(&pb)),
ConflictClass::DriverResolvable
);
}
#[test]
fn test_classify_missing_patches() {
let conflict = Conflict::new(test_hash("a"), test_hash("b"), vec!["f1".to_string()]);
assert_eq!(conflict.classify(None, None), ConflictClass::Genuine);
assert_eq!(conflict.classify(None, None), ConflictClass::Genuine);
}
}