suture_core/patch/
commute.rs1use crate::patch::types::Patch;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum CommuteResult {
12 Commutes,
14 DoesNotCommute {
16 conflict_addresses: Vec<String>,
18 },
19}
20
21pub fn commute(p1: &Patch, p2: &Patch) -> CommuteResult {
33 if p1.is_identity() || p2.is_identity() {
35 return CommuteResult::Commutes;
36 }
37
38 if !p1.touch_set.intersects(&p2.touch_set) {
39 CommuteResult::Commutes
40 } else {
41 let intersection = p1.touch_set.intersection(&p2.touch_set);
42 let conflict_addresses: Vec<String> = intersection.iter().cloned().collect();
43 CommuteResult::DoesNotCommute { conflict_addresses }
44 }
45}
46
47#[allow(dead_code)]
52pub fn all_commute(patches: &[Patch]) -> bool {
53 for i in 0..patches.len() {
54 for j in (i + 1)..patches.len() {
55 if !matches!(commute(&patches[i], &patches[j]), CommuteResult::Commutes) {
56 return false;
57 }
58 }
59 }
60 true
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::patch::types::{OperationType, Patch, TouchSet};
67 use suture_common::Hash;
68
69 fn patch_with_touch(addr: &str) -> Patch {
70 Patch::new(
71 OperationType::Modify,
72 TouchSet::single(addr),
73 Some(format!("file_{}", addr)),
74 vec![],
75 vec![],
76 "test".to_string(),
77 format!("edit {}", addr),
78 )
79 }
80
81 fn patch_with_touches(addrs: &[&str]) -> Patch {
82 Patch::new(
83 OperationType::Modify,
84 TouchSet::from_addrs(addrs.iter().copied()),
85 None,
86 vec![],
87 vec![],
88 "test".to_string(),
89 "multi edit".to_string(),
90 )
91 }
92
93 #[test]
94 fn test_disjoint_patches_commute() {
95 let p1 = patch_with_touch("A1");
96 let p2 = patch_with_touch("B1");
97 assert_eq!(commute(&p1, &p2), CommuteResult::Commutes);
98 }
99
100 #[test]
101 fn test_overlapping_patches_do_not_commute() {
102 let p1 = patch_with_touch("A1");
103 let p2 = patch_with_touch("A1");
104 assert_eq!(
105 commute(&p1, &p2),
106 CommuteResult::DoesNotCommute {
107 conflict_addresses: vec!["A1".to_string()]
108 }
109 );
110 }
111
112 #[test]
113 fn test_identity_commutes_with_everything() {
114 let parent = Hash::ZERO;
115 let identity = Patch::identity(parent, "test".to_string());
116 let p = patch_with_touch("A1");
117 assert_eq!(commute(&identity, &p), CommuteResult::Commutes);
118 assert_eq!(commute(&p, &identity), CommuteResult::Commutes);
119 }
120
121 #[test]
122 fn test_partial_overlap() {
123 let p1 = patch_with_touches(&["A1", "B1", "C1"]);
124 let p2 = patch_with_touches(&["C1", "D1", "E1"]);
125
126 match commute(&p1, &p2) {
127 CommuteResult::DoesNotCommute { conflict_addresses } => {
128 assert_eq!(conflict_addresses, vec!["C1".to_string()]);
129 }
130 CommuteResult::Commutes => panic!("Expected DoesNotCommute"),
131 }
132 }
133
134 #[test]
135 fn test_commutativity_is_symmetric() {
136 let p1 = patch_with_touch("A1");
137 let p2 = patch_with_touch("B1");
138 assert_eq!(commute(&p1, &p2), commute(&p2, &p1));
139 }
140
141 #[test]
142 fn test_all_commute_empty() {
143 assert!(all_commute(&[]));
144 }
145
146 #[test]
147 fn test_all_commute_single() {
148 let p = patch_with_touch("A1");
149 assert!(all_commute(&[p]));
150 }
151
152 #[test]
153 fn test_all_commute_disjoint() {
154 let patches = vec![
155 patch_with_touch("A1"),
156 patch_with_touch("B1"),
157 patch_with_touch("C1"),
158 ];
159 assert!(all_commute(&patches));
160 }
161
162 #[test]
163 fn test_all_commute_with_overlap() {
164 let patches = vec![
165 patch_with_touch("A1"),
166 patch_with_touch("B1"),
167 patch_with_touch("A1"), ];
169 assert!(!all_commute(&patches));
170 }
171}