1pub mod error;
2use error::*;
3
4mod convergence;
5use convergence::Convergence;
6
7mod quorum;
8use quorum::{CommitQuorum, CommitQuorumFailure, TagQuorum, TagQuorumFailure};
9
10mod voting;
11
12pub mod effects;
13pub mod rules;
14
15pub use rules::{MatchedRule, RawRule, Rules, ValidRule};
16
17use std::collections::{BTreeMap, BTreeSet};
18use std::fmt;
19use std::marker::PhantomData;
20use std::ops::ControlFlow;
21
22use crate::git::fmt::Namespaced;
23
24use crate::prelude::Did;
25
26use super::fmt::Qualified;
27use crate::git::Oid;
28
29pub struct Initial;
32
33pub struct ObjectsFound;
36
37pub struct Canonical<'a, 'b, 'r, R, T> {
49 refname: Qualified<'a>,
50 rule: &'b ValidRule,
51 repo: &'r R,
52 objects: BTreeMap<Did, Object>,
53 missing: Missing,
54 _marker: PhantomData<T>,
55}
56
57impl<'a, 'b, 'r, R, T> fmt::Debug for Canonical<'a, 'b, 'r, R, T> {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.debug_struct("Canonical")
60 .field("refname", &self.refname)
61 .field("rule", &self.rule)
62 .field("objects", &self.objects)
63 .field("missing", &self.missing)
64 .finish()
65 }
66}
67
68pub struct CanonicalWithConvergence<'a, 'b, 'r, R> {
72 canonical: Canonical<'a, 'b, 'r, R, ObjectsFound>,
73 convergence: Convergence<'r, R>,
74}
75
76impl<'a, 'b, 'r, R> fmt::Debug for CanonicalWithConvergence<'a, 'b, 'r, R> {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.debug_struct("CanonicalWithConvergence")
79 .field("canonical", &self.canonical)
80 .field("convergence", &self.convergence)
81 .finish()
82 }
83}
84
85impl<'a, 'b, 'r, R> AsRef<Canonical<'a, 'b, 'r, R, ObjectsFound>>
86 for CanonicalWithConvergence<'a, 'b, 'r, R>
87{
88 fn as_ref(&self) -> &Canonical<'a, 'b, 'r, R, ObjectsFound> {
89 &self.canonical
90 }
91}
92
93impl<'a, 'b, 'r, R> Canonical<'a, 'b, 'r, R, Initial>
94where
95 R: effects::Ancestry + effects::FindMergeBase + effects::FindObjects,
96{
97 pub fn new(refname: Qualified<'a>, rule: &'b ValidRule, repo: &'r R) -> Self {
101 Self {
102 refname,
103 rule,
104 repo,
105 missing: Missing::default(),
106 objects: BTreeMap::new(),
107 _marker: PhantomData,
108 }
109 }
110
111 pub fn find_objects(
114 self,
115 ) -> Result<Canonical<'a, 'b, 'r, R, ObjectsFound>, effects::FindObjectsError> {
116 let FoundObjects {
117 objects,
118 missing_refs,
119 missing_objects,
120 } = self
121 .repo
122 .find_objects(&self.refname, self.rule.allowed().iter())?;
123 let missing = Missing {
124 refs: missing_refs,
125 objects: missing_objects,
126 };
127 Ok(Canonical {
128 refname: self.refname,
129 rule: self.rule,
130 repo: self.repo,
131 missing,
132 objects,
133 _marker: PhantomData,
134 })
135 }
136}
137
138impl<'a, 'b, 'r, R> Canonical<'a, 'b, 'r, R, ObjectsFound>
139where
140 R: effects::Ancestry + effects::FindMergeBase + effects::FindObjects,
141{
142 pub fn with_convergence(
144 self,
145 candidate: Did,
146 object: Object,
147 ) -> CanonicalWithConvergence<'a, 'b, 'r, R> {
148 let convergence = Convergence::new(self.repo, candidate, object);
149 CanonicalWithConvergence {
150 canonical: self,
151 convergence,
152 }
153 }
154
155 pub fn quorum(self) -> Result<Quorum<'a>, QuorumError> {
157 let mut finder = QuorumFinder::new(self.refname, self.rule, self.objects.values());
158 while let ControlFlow::Continue(pairs) = finder.find_merge_bases() {
159 let mut bases = Vec::with_capacity(pairs.size_hint().0);
160 for (a, b) in pairs {
161 bases.push(self.repo.merge_base(a, b)?);
162 }
163 finder.found_merge_bases(bases.into_iter());
164 }
165 let refname = finder.refname.clone();
166 let threshold = (*finder.rule.threshold()).into();
167 let results = finder.find_quorum();
168 match results {
169 (Ok(commit), Err(_)) => Ok(commit),
170 (Err(_), Ok(tag)) => Ok(tag),
171 (Ok(_), Ok(_)) => Err(QuorumError::DifferentTypes {
172 refname: refname.to_string(),
173 }),
174 (Err(ec), Err(eq)) => Err(Self::convert_failures(
175 ec,
176 eq,
177 refname.to_string(),
178 threshold,
179 )),
180 }
181 }
182
183 pub fn missing(&self) -> &Missing {
187 &self.missing
188 }
189
190 pub fn found_objects(&mut self, objects: impl IntoIterator<Item = (Did, Object)>) {
193 let objects = objects.into_iter();
194 for (did, object) in objects {
195 self.missing.found(&did, &self.refname);
196 self.objects.insert(did, object);
197 }
198 }
199
200 fn convert_failures(
201 commit: CommitQuorumFailure,
202 tag: TagQuorumFailure,
203 refname: String,
204 threshold: usize,
205 ) -> QuorumError {
206 match (commit, tag) {
207 (CommitQuorumFailure::NoCandidates, TagQuorumFailure::NoCandidates) => {
208 QuorumError::NoCandidates { refname, threshold }
209 }
210 (CommitQuorumFailure::NoCandidates, TagQuorumFailure::DivergingTags { candidates }) => {
211 QuorumError::DivergingTags {
212 refname,
213 threshold,
214 candidates,
215 }
216 }
217 (
218 CommitQuorumFailure::DivergingCommits {
219 base,
220 longest,
221 candidate,
222 },
223 _,
224 ) => QuorumError::DivergingCommits {
225 refname,
226 threshold,
227 base,
228 longest,
229 head: candidate,
230 },
231 (CommitQuorumFailure::NoMergeBase { a, b }, _) => {
232 #[derive(thiserror::Error, Debug)]
233 #[error("no existing merge base found for commit quorum")]
234 struct NoMergeBase;
235
236 effects::MergeBaseError::new(a, b, NoMergeBase).into()
237 }
238 }
239 }
240}
241
242impl<'a, 'b, 'r, R> CanonicalWithConvergence<'a, 'b, 'r, R>
243where
244 R: effects::Ancestry + effects::FindMergeBase + effects::FindObjects,
245{
246 pub fn quorum(mut self) -> Result<QuorumWithConvergence<'a>, QuorumError> {
248 let converges = match self.convergence.check(self.canonical.objects.iter())? {
249 Some((candidate, object)) => {
250 if self.canonical.objects.is_empty()
251 || self.canonical.rule.allowed().is_only(&candidate)
252 {
253 self.canonical.objects.insert(candidate, object);
254 }
255 true
256 }
257 None => false,
258 };
259 let quorum = self.canonical.quorum()?;
260 Ok(QuorumWithConvergence { quorum, converges })
261 }
262}
263
264#[derive(Clone, Debug, PartialEq, Eq)]
266pub struct Quorum<'a> {
267 pub refname: Qualified<'a>,
269 pub object: Object,
271}
272
273#[derive(Clone, Debug, PartialEq, Eq)]
276pub struct QuorumWithConvergence<'a> {
277 pub quorum: Quorum<'a>,
278 pub converges: bool,
279}
280
281#[derive(Debug)]
284struct QuorumFinder<'a, 'b> {
285 refname: Qualified<'a>,
286 rule: &'b ValidRule,
287 tag_quorum: TagQuorum,
288 commit_quorum: CommitQuorum,
289}
290
291impl<'a, 'b> QuorumFinder<'a, 'b> {
292 fn new<'c, I>(refname: Qualified<'a>, rule: &'b ValidRule, objects: I) -> Self
293 where
294 I: Iterator<Item = &'c Object> + Clone,
295 {
296 let threshold = *rule.threshold();
297 let tag_quorum = TagQuorum::new(objects.clone(), threshold.into());
298 let commit_quorum = CommitQuorum::new(objects, threshold.into());
299 Self {
300 refname,
301 rule,
302 tag_quorum,
303 commit_quorum,
304 }
305 }
306
307 fn find_merge_bases(&mut self) -> ControlFlow<(), impl Iterator<Item = (Oid, Oid)>> {
308 match self.commit_quorum.next_candidate() {
309 Some(candidate) => ControlFlow::Continue(candidate),
310 None => ControlFlow::Break(()),
311 }
312 }
313
314 fn found_merge_bases<I>(&mut self, bases: I)
315 where
316 I: Iterator<Item = MergeBase>,
317 {
318 self.commit_quorum.found_merge_bases(bases);
319 }
320
321 fn find_quorum(
322 self,
323 ) -> (
324 Result<Quorum<'a>, quorum::CommitQuorumFailure>,
325 Result<Quorum<'a>, quorum::TagQuorumFailure>,
326 ) {
327 let commit = self.commit_quorum.find_quorum().map(|id| Quorum {
328 refname: self.refname.clone(),
329 object: Object::Commit { id },
330 });
331 let tag = self.tag_quorum.find_quorum().map(|id| Quorum {
332 refname: self.refname.clone(),
333 object: Object::Tag { id },
334 });
335 (commit, tag)
336 }
337}
338
339#[derive(Clone, Copy, Debug, PartialEq, Eq)]
341pub struct MergeBase {
342 pub a: Oid,
344 pub b: Oid,
346 pub base: Oid,
348}
349
350impl MergeBase {
351 #[cfg(test)]
353 pub fn trivial(oid: Oid) -> Self {
354 Self {
355 a: oid,
356 b: oid,
357 base: oid,
358 }
359 }
360
361 pub fn is_trivial(&self) -> bool {
363 if self.a == self.b {
364 debug_assert_eq!(self.a, self.base);
368 debug_assert_eq!(self.b, self.base);
369 true
370 } else {
371 false
372 }
373 }
374
375 pub fn linear(self) -> Option<Oid> {
378 if self.is_trivial() || (self.a != self.base && self.b != self.base) {
379 None
380 } else {
381 Some(self.base)
382 }
383 }
384}
385
386#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
388pub enum Object {
389 Commit { id: Oid },
390 Tag { id: Oid },
391}
392
393impl Object {
394 pub fn new(obj: &crate::git::raw::Object) -> Option<Self> {
395 obj.kind().and_then(|kind| match kind {
396 crate::git::raw::ObjectType::Commit => Some(Self::Commit {
397 id: obj.id().into(),
398 }),
399 crate::git::raw::ObjectType::Tag => Some(Self::Tag {
400 id: obj.id().into(),
401 }),
402 _ => None,
403 })
404 }
405
406 pub fn id(&self) -> Oid {
408 match self {
409 Object::Commit { id } => *id,
410 Object::Tag { id } => *id,
411 }
412 }
413
414 pub fn is_commit(&self) -> bool {
416 matches!(self, Self::Commit { .. })
417 }
418
419 pub fn is_tag(&self) -> bool {
421 matches!(self, Self::Commit { .. })
422 }
423
424 pub fn object_type(&self) -> ObjectType {
426 match self {
427 Object::Commit { .. } => ObjectType::Commit,
428 Object::Tag { .. } => ObjectType::Tag,
429 }
430 }
431}
432
433#[derive(Clone, Copy, Debug, PartialEq, Eq)]
435pub enum ObjectType {
436 Commit,
438 Tag,
440}
441
442impl fmt::Display for ObjectType {
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444 match self {
445 ObjectType::Commit => f.write_str("commit"),
446 ObjectType::Tag => f.write_str("tag"),
447 }
448 }
449}
450
451#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
453pub struct GraphAheadBehind {
454 pub ahead: usize,
456 pub behind: usize,
458}
459
460impl GraphAheadBehind {
461 pub fn is_linear(&self) -> bool {
469 self.ahead * self.behind == 0
470 }
471}
472
473#[derive(Clone, Debug, PartialEq, Eq)]
475pub struct FoundObjects {
476 pub objects: BTreeMap<Did, Object>,
478 pub missing_refs: BTreeSet<Namespaced<'static>>,
480 pub missing_objects: BTreeMap<Did, Oid>,
485}
486
487#[derive(Clone, Debug, Default, PartialEq, Eq)]
489pub struct Missing {
490 pub refs: BTreeSet<Namespaced<'static>>,
491 pub objects: BTreeMap<Did, Oid>,
492}
493
494impl Missing {
495 fn found<'a>(&mut self, did: &Did, refname: &Qualified<'a>) {
496 self.objects.remove(did);
497 self.refs
498 .remove(&refname.with_namespace((did.as_key()).into()).to_owned());
499 }
500}
501
502#[cfg(test)]
503#[allow(clippy::unwrap_used)]
504mod tests {
505
506 use super::*;
507 use crate::assert_matches;
508 use crate::git;
509 use crate::node::device::Device;
510 use crate::test::fixtures;
511
512 fn quorum(
514 heads: &[crate::git::Oid],
515 threshold: usize,
516 repo: &crate::git::raw::Repository,
517 ) -> Result<Oid, QuorumError> {
518 let refname = git::refs::branch(crate::git::fmt::RefStr::try_from_str("master").unwrap());
519
520 let mut delegates = Vec::new();
521 for (i, head) in heads.iter().enumerate() {
522 let signer = Device::mock_from_seed([(i + 1) as u8; 32]);
523 let did = Did::from(signer.public_key());
524 delegates.push(did);
525 let ns = git::fmt::Component::from(signer.public_key());
526 repo.reference(refname.with_namespace(ns).as_str(), head.into(), true, "")
527 .unwrap();
528 }
529
530 let rule: RawRule = crate::git::canonical::rules::Rule::new(
531 crate::git::canonical::rules::Allowed::Delegates,
532 threshold,
533 );
534 let delegates = crate::identity::doc::Delegates::new(delegates).unwrap();
535 let rule = rule.validate(&mut || delegates.clone()).unwrap();
536
537 Canonical::new(refname, &rule, repo)
538 .find_objects()
539 .unwrap()
540 .quorum()
541 .map(|Quorum { object, .. }| object.id())
542 }
543
544 fn commit(id: &str) -> Object {
545 Object::Commit {
546 id: id.parse().unwrap(),
547 }
548 }
549
550 fn tag(id: &str) -> Object {
551 Object::Tag {
552 id: id.parse().unwrap(),
553 }
554 }
555
556 #[test]
557 fn test_quorum_properties() {
558 let tmp = tempfile::tempdir().unwrap();
559 let (repo, c0) = fixtures::repository(tmp.path());
560 let c0: crate::git::Oid = c0.into();
561 let a1 = fixtures::commit("A1", &[c0.into()], &repo);
562 let a2 = fixtures::commit("A2", &[a1.into()], &repo);
563 let d1 = fixtures::commit("D1", &[c0.into()], &repo);
564 let c1 = fixtures::commit("C1", &[c0.into()], &repo);
565 let c2 = fixtures::commit("C2", &[c1.into()], &repo);
566 let b2 = fixtures::commit("B2", &[c1.into()], &repo);
567 let a1 = fixtures::commit("A1", &[c0.into()], &repo);
568 let m1 = fixtures::commit("M1", &[c2.into(), b2.into()], &repo);
569 let m2 = fixtures::commit("M2", &[a1.into(), b2.into()], &repo);
570 let mut rng = fastrand::Rng::new();
571 let choices = [c0, c1, c2, b2, a1, a2, d1, m1, m2];
572
573 for _ in 0..100 {
574 let count = rng.usize(1..=choices.len());
575 let threshold = rng.usize(1..=count);
576 let mut heads = Vec::new();
577
578 for _ in 0..count {
579 let ix = rng.usize(0..choices.len());
580 heads.push(choices[ix]);
581 }
582 rng.shuffle(&mut heads);
583
584 if let Ok(canonical) = quorum(&heads, threshold, &repo) {
585 assert!(heads.contains(&canonical));
586 }
587 }
588 }
589
590 #[test]
591 fn test_quorum_different_types() {
592 let tmp = tempfile::tempdir().unwrap();
593 let (repo, c0) = fixtures::repository(tmp.path());
594 let t0 = fixtures::tag("v1", "", c0, &repo);
595
596 assert_matches!(
597 quorum(&[c0.into(), t0], 1, &repo),
598 Err(QuorumError::DifferentTypes { .. })
599 );
600 }
601
602 #[test]
603 fn test_commit_quorum_groups() {
604 let c0 = commit("f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354");
605 let c1 = commit("bfb1a513e420eade90b0e6be64117b861b16ecb5");
606 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
607
608 let mut cq = CommitQuorum::new([c1, c2, c1, c2].iter(), 2);
613 cq.found_merge_bases([MergeBase {
614 a: c1.id(),
615 b: c2.id(),
616 base: c0.id(),
617 }]);
618 assert_matches!(
619 cq.find_quorum(),
620 Err(CommitQuorumFailure::DivergingCommits { .. })
621 );
622
623 let mut cq = CommitQuorum::new([c1, c2].iter(), 1);
624 cq.found_merge_bases([MergeBase {
625 a: c1.id(),
626 b: c2.id(),
627 base: c0.id(),
628 }]);
629 assert_matches!(
630 cq.find_quorum(),
631 Err(CommitQuorumFailure::DivergingCommits { .. })
632 );
633 }
634
635 #[test]
636 fn test_tag_quorum() {
637 let t1 = tag("0480391dd7312d35c79a455ec5d004657260b358");
638 let t2 = tag("a2eec713ec5c287ecdf13a0180f68acfef7962d0");
639
640 assert_eq!(
641 TagQuorum::new([t1].iter(), 1).find_quorum().unwrap(),
642 t1.id()
643 );
644 assert_eq!(
645 TagQuorum::new([t1, t1].iter(), 2).find_quorum().unwrap(),
646 t1.id()
647 );
648 assert_matches!(
649 TagQuorum::new([t1, t2].iter(), 1).find_quorum(),
650 Err(TagQuorumFailure::DivergingTags { .. })
651 );
652 }
653
654 #[test]
655 fn test_commit_quorum_single() {
656 let c0 = commit("f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354");
657 let c1 = commit("bfb1a513e420eade90b0e6be64117b861b16ecb5");
658 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
659 assert_eq!(
660 CommitQuorum::new([c0].iter(), 1).find_quorum().unwrap(),
661 c0.id()
662 );
663 assert_eq!(
664 CommitQuorum::new([c1].iter(), 1).find_quorum().unwrap(),
665 c1.id()
666 );
667 assert_eq!(
668 CommitQuorum::new([c2].iter(), 1).find_quorum().unwrap(),
669 c2.id()
670 );
671 }
672
673 #[test]
674 fn test_commit_quorum_linear() {
675 let c0 = commit("f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354");
676 let c1 = commit("bfb1a513e420eade90b0e6be64117b861b16ecb5");
677 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
678 let merge_bases = [
684 MergeBase {
685 a: c2.id(),
686 b: c1.id(),
687 base: c1.id(),
688 },
689 MergeBase {
690 a: c2.id(),
691 b: c0.id(),
692 base: c0.id(),
693 },
694 MergeBase {
695 a: c1.id(),
696 b: c0.id(),
697 base: c0.id(),
698 },
699 MergeBase::trivial(c2.id()),
700 MergeBase::trivial(c1.id()),
701 MergeBase::trivial(c0.id()),
702 ];
703
704 let mut cq = CommitQuorum::new([c1, c2].iter(), 1);
705 cq.found_merge_bases(merge_bases);
706 assert_eq!(cq.find_quorum().unwrap(), c2.id());
707
708 let mut cq = CommitQuorum::new([c1, c2].iter(), 2);
709 cq.found_merge_bases(merge_bases);
710 assert_eq!(cq.find_quorum().unwrap(), c1.id());
711
712 let mut cq = CommitQuorum::new([c0, c1, c2].iter(), 3);
713 cq.found_merge_bases(merge_bases);
714 assert_eq!(cq.find_quorum().unwrap(), c0.id());
715
716 let mut cq = CommitQuorum::new([c1, c1, c2].iter(), 2);
717 cq.found_merge_bases(merge_bases);
718 assert_eq!(cq.find_quorum().unwrap(), c1.id());
719
720 let mut cq = CommitQuorum::new([c1, c1, c2].iter(), 1);
721 cq.found_merge_bases(merge_bases);
722 assert_eq!(cq.find_quorum().unwrap(), c2.id());
723
724 let mut cq = CommitQuorum::new([c2, c2, c1].iter(), 1);
725 cq.found_merge_bases(merge_bases);
726 assert_eq!(cq.find_quorum().unwrap(), c2.id());
727 }
728
729 #[test]
730 fn test_commit_quorum_two_way_fork() {
731 let c0 = commit("f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354");
732 let c1 = commit("bfb1a513e420eade90b0e6be64117b861b16ecb5");
733 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
734 let b2 = commit("037a148170e3d41524b7c482a4798e5c2daeaa00");
735
736 eprintln!("C0: {}", c0.id());
737 eprintln!("C1: {}", c1.id());
738 eprintln!("C2: {}", c2.id());
739 eprintln!("B2: {}", b2.id());
740
741 let merge_bases = [
747 MergeBase {
748 a: b2.id(),
749 b: c2.id(),
750 base: c1.id(),
751 },
752 MergeBase {
753 a: c2.id(),
754 b: c1.id(),
755 base: c1.id(),
756 },
757 MergeBase {
758 a: b2.id(),
759 b: c1.id(),
760 base: c1.id(),
761 },
762 MergeBase {
763 a: c1.id(),
764 b: c0.id(),
765 base: c0.id(),
766 },
767 MergeBase::trivial(b2.id()),
768 MergeBase::trivial(c2.id()),
769 MergeBase::trivial(c1.id()),
770 MergeBase::trivial(c0.id()),
771 ];
772
773 let mut cq = CommitQuorum::new([c1, c2, b2].iter(), 1);
774 cq.found_merge_bases(merge_bases);
775 assert_matches!(
776 cq.find_quorum(),
777 Err(CommitQuorumFailure::DivergingCommits { .. })
778 );
779
780 let mut cq = CommitQuorum::new([c2, b2].iter(), 1);
781 cq.found_merge_bases(merge_bases);
782 assert_matches!(
783 cq.find_quorum(),
784 Err(CommitQuorumFailure::DivergingCommits { .. })
785 );
786
787 let mut cq = CommitQuorum::new([b2, c2].iter(), 1);
788 cq.found_merge_bases(merge_bases);
789 assert_matches!(
790 cq.find_quorum(),
791 Err(CommitQuorumFailure::DivergingCommits { .. })
792 );
793
794 let mut cq = CommitQuorum::new([c2, b2].iter(), 2);
798 cq.found_merge_bases([MergeBase {
799 a: b2.id(),
800 b: c2.id(),
801 base: c1.id(),
802 }]);
803 assert_matches!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
804
805 let mut cq = CommitQuorum::new([b2, c2].iter(), 2);
806 cq.found_merge_bases([MergeBase {
807 a: b2.id(),
808 b: c2.id(),
809 base: c1.id(),
810 }]);
811 assert_matches!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
812
813 let mut cq = CommitQuorum::new([c1, c2, b2].iter(), 2);
814 cq.found_merge_bases(merge_bases);
815 assert_eq!(cq.find_quorum().unwrap(), c1.id());
816
817 let mut cq = CommitQuorum::new([c1, c2, b2].iter(), 3);
818 cq.found_merge_bases(merge_bases);
819 assert_eq!(cq.find_quorum().unwrap(), c1.id());
820
821 let mut cq = CommitQuorum::new([b2, b2, c2].iter(), 2);
822 cq.found_merge_bases(merge_bases);
823 assert_eq!(cq.find_quorum().unwrap(), b2.id());
824
825 let mut cq = CommitQuorum::new([b2, c2, c2].iter(), 2);
826 cq.found_merge_bases(merge_bases);
827 assert_eq!(cq.find_quorum().unwrap(), c2.id());
828
829 let mut cq = CommitQuorum::new([b2, b2, c2, c2].iter(), 2);
830 cq.found_merge_bases(merge_bases);
831 assert_matches!(
832 cq.find_quorum(),
833 Err(CommitQuorumFailure::DivergingCommits { .. })
834 );
835 }
836
837 #[test]
838 fn test_commit_quorum_three_way_fork() {
839 let c1 = commit("bfb1a513e420eade90b0e6be64117b861b16ecb5");
840 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
841 let c3 = commit("07c2a0f856e0d6b08115f98a265df88c4e507fa0");
842 let b2 = commit("037a148170e3d41524b7c482a4798e5c2daeaa00");
843
844 let mut cq = CommitQuorum::new([b2, c2, c2].iter(), 2);
850 cq.found_merge_bases([
851 MergeBase {
852 a: b2.id(),
853 b: c2.id(),
854 base: c1.id(),
855 },
856 MergeBase::trivial(b2.id()),
857 ]);
858 assert_eq!(cq.find_quorum().unwrap(), c2.id());
859
860 let mut cq = CommitQuorum::new([b2, c2, c2].iter(), 3);
861 cq.found_merge_bases([
862 MergeBase {
863 a: b2.id(),
864 b: c2.id(),
865 base: c1.id(),
866 },
867 MergeBase::trivial(c2.id()),
868 ]);
869 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
870
871 let mut cq = CommitQuorum::new([b2, c2, b2, c2].iter(), 3);
872 cq.found_merge_bases([
873 MergeBase {
874 a: b2.id(),
875 b: c2.id(),
876 base: c1.id(),
877 },
878 MergeBase {
879 a: c2.id(),
880 b: b2.id(),
881 base: c1.id(),
882 },
883 MergeBase::trivial(b2.id()),
884 MergeBase::trivial(c2.id()),
885 ]);
886 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
887
888 let mut cq = CommitQuorum::new([c3, b2, c2, b2, c2, c3].iter(), 3);
889 cq.found_merge_bases([
890 MergeBase {
891 a: c3.id(),
892 b: b2.id(),
893 base: c1.id(),
894 },
895 MergeBase {
896 a: c3.id(),
897 b: c2.id(),
898 base: c1.id(),
899 },
900 MergeBase {
901 a: b2.id(),
902 b: c2.id(),
903 base: c1.id(),
904 },
905 MergeBase {
906 a: c2.id(),
907 b: b2.id(),
908 base: c1.id(),
909 },
910 MergeBase::trivial(b2.id()),
911 MergeBase::trivial(c2.id()),
912 MergeBase::trivial(c3.id()),
913 ]);
914 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
915 }
916
917 #[test]
918 fn test_commit_quorum_fork_of_a_fork() {
919 let c0 = commit("f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354");
920 let c1 = commit("bfb1a513e420eade90b0e6be64117b861b16ecb5");
921 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
922 let b2 = commit("037a148170e3d41524b7c482a4798e5c2daeaa00");
923 let a1 = commit("2224468e22b30359611d880ccf0850d023f86f9b");
924
925 let mut cq = CommitQuorum::new([c2, b2, a1].iter(), 1);
931 cq.found_merge_bases([
932 MergeBase {
933 a: c2.id(),
934 b: b2.id(),
935 base: c1.id(),
936 },
937 MergeBase {
938 a: c2.id(),
939 b: a1.id(),
940 base: c0.id(),
941 },
942 MergeBase {
943 a: b2.id(),
944 b: a1.id(),
945 base: c0.id(),
946 },
947 ]);
948 assert_matches!(
949 cq.find_quorum(),
950 Err(CommitQuorumFailure::DivergingCommits { .. })
951 );
952 let mut cq = CommitQuorum::new([c2, b2, a1].iter(), 2);
953 cq.found_merge_bases([
954 MergeBase {
955 a: c2.id(),
956 b: b2.id(),
957 base: c1.id(),
958 },
959 MergeBase {
960 a: c2.id(),
961 b: a1.id(),
962 base: c0.id(),
963 },
964 MergeBase {
965 a: b2.id(),
966 b: a1.id(),
967 base: c0.id(),
968 },
969 ]);
970 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
971
972 let mut cq = CommitQuorum::new([c2, b2, a1].iter(), 3);
973 cq.found_merge_bases([
974 MergeBase {
975 a: c2.id(),
976 b: b2.id(),
977 base: c1.id(),
978 },
979 MergeBase {
980 a: c2.id(),
981 b: a1.id(),
982 base: c0.id(),
983 },
984 MergeBase {
985 a: b2.id(),
986 b: a1.id(),
987 base: c0.id(),
988 },
989 ]);
990 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
991
992 let mut cq = CommitQuorum::new([c1, c2, b2, a1].iter(), 4);
993 cq.found_merge_bases([
994 MergeBase {
995 a: c1.id(),
996 b: c2.id(),
997 base: c1.id(),
998 },
999 MergeBase {
1000 a: c1.id(),
1001 b: b2.id(),
1002 base: c1.id(),
1003 },
1004 MergeBase {
1005 a: c1.id(),
1006 b: a1.id(),
1007 base: c0.id(),
1008 },
1009 MergeBase {
1010 a: c2.id(),
1011 b: b2.id(),
1012 base: c1.id(),
1013 },
1014 MergeBase {
1015 a: c2.id(),
1016 b: a1.id(),
1017 base: c0.id(),
1018 },
1019 MergeBase {
1020 a: b2.id(),
1021 b: a1.id(),
1022 base: c0.id(),
1023 },
1024 ]);
1025 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
1026
1027 let all_merge_bases = [
1028 MergeBase {
1029 a: c0.id(),
1030 b: c1.id(),
1031 base: c0.id(),
1032 },
1033 MergeBase {
1034 a: c0.id(),
1035 b: c2.id(),
1036 base: c0.id(),
1037 },
1038 MergeBase {
1039 a: c0.id(),
1040 b: b2.id(),
1041 base: c0.id(),
1042 },
1043 MergeBase {
1044 a: c0.id(),
1045 b: a1.id(),
1046 base: c0.id(),
1047 },
1048 MergeBase {
1049 a: c1.id(),
1050 b: c2.id(),
1051 base: c1.id(),
1052 },
1053 MergeBase {
1054 a: c1.id(),
1055 b: b2.id(),
1056 base: c1.id(),
1057 },
1058 MergeBase {
1059 a: c1.id(),
1060 b: a1.id(),
1061 base: c0.id(),
1062 },
1063 MergeBase {
1064 a: c2.id(),
1065 b: b2.id(),
1066 base: c1.id(),
1067 },
1068 MergeBase {
1069 a: c2.id(),
1070 b: a1.id(),
1071 base: c0.id(),
1072 },
1073 MergeBase {
1074 a: b2.id(),
1075 b: a1.id(),
1076 base: c0.id(),
1077 },
1078 ];
1079 let mut cq = CommitQuorum::new([c0, c1, c2, b2, a1].iter(), 2);
1080 cq.found_merge_bases(all_merge_bases);
1081 assert_eq!(cq.find_quorum().unwrap(), c1.id());
1082
1083 let mut cq = CommitQuorum::new([c0, c1, c2, b2, a1].iter(), 3);
1084 cq.found_merge_bases(all_merge_bases);
1085 assert_eq!(cq.find_quorum().unwrap(), c1.id());
1086
1087 let mut cq = CommitQuorum::new([c0, c2, b2, a1].iter(), 3);
1088 cq.found_merge_bases(all_merge_bases);
1089 assert_eq!(cq.find_quorum().unwrap(), c0.id());
1090
1091 let mut cq = CommitQuorum::new([c0, c1, c2, b2, a1].iter(), 4);
1092 cq.found_merge_bases(all_merge_bases);
1093 assert_eq!(cq.find_quorum().unwrap(), c0.id());
1094
1095 let mut cq = CommitQuorum::new([a1, a1, c2, c2, c1].iter(), 2);
1096 cq.found_merge_bases([
1097 MergeBase::trivial(a1.id()),
1098 MergeBase::trivial(c2.id()),
1099 MergeBase {
1100 a: a1.id(),
1101 b: c2.id(),
1102 base: c0.id(),
1103 },
1104 MergeBase {
1105 a: a1.id(),
1106 b: c1.id(),
1107 base: c0.id(),
1108 },
1109 MergeBase {
1110 a: c2.id(),
1111 b: c1.id(),
1112 base: c0.id(),
1113 },
1114 ]);
1115 assert_matches!(
1116 cq.find_quorum(),
1117 Err(CommitQuorumFailure::DivergingCommits { .. })
1118 );
1119
1120 let mut cq = CommitQuorum::new([a1, a1, c2, c2, c1].iter(), 1);
1121 cq.found_merge_bases([
1122 MergeBase::trivial(a1.id()),
1123 MergeBase::trivial(c2.id()),
1124 MergeBase {
1125 a: a1.id(),
1126 b: c2.id(),
1127 base: c0.id(),
1128 },
1129 MergeBase {
1130 a: a1.id(),
1131 b: c1.id(),
1132 base: c0.id(),
1133 },
1134 MergeBase {
1135 a: c2.id(),
1136 b: c1.id(),
1137 base: c0.id(),
1138 },
1139 ]);
1140 assert_matches!(
1141 cq.find_quorum(),
1142 Err(CommitQuorumFailure::DivergingCommits { .. })
1143 );
1144
1145 let mut cq = CommitQuorum::new([a1, a1, c2, c2, c1].iter(), 1);
1146 cq.found_merge_bases([
1147 MergeBase::trivial(a1.id()),
1148 MergeBase {
1149 a: a1.id(),
1150 b: c2.id(),
1151 base: c0.id(),
1152 },
1153 ]);
1154 assert_matches!(
1155 cq.find_quorum(),
1156 Err(CommitQuorumFailure::DivergingCommits { .. })
1157 );
1158
1159 let mut cq = CommitQuorum::new([b2, b2, c2, c2].iter(), 1);
1160 cq.found_merge_bases([
1161 MergeBase::trivial(b2.id()),
1162 MergeBase::trivial(c2.id()),
1163 MergeBase {
1164 a: b2.id(),
1165 b: c2.id(),
1166 base: c1.id(),
1167 },
1168 ]);
1169 assert_matches!(
1170 cq.find_quorum(),
1171 Err(CommitQuorumFailure::DivergingCommits { .. })
1172 );
1173
1174 let mut cq = CommitQuorum::new([b2, b2, c2, c2, a1].iter(), 1);
1175 cq.found_merge_bases([
1176 MergeBase::trivial(b2.id()),
1177 MergeBase::trivial(c2.id()),
1178 MergeBase {
1179 a: b2.id(),
1180 b: c2.id(),
1181 base: c1.id(),
1182 },
1183 MergeBase {
1184 a: b2.id(),
1185 b: a1.id(),
1186 base: c0.id(),
1187 },
1188 MergeBase {
1189 a: c2.id(),
1190 b: a1.id(),
1191 base: c0.id(),
1192 },
1193 ]);
1194 assert_matches!(
1195 cq.find_quorum(),
1196 Err(CommitQuorumFailure::DivergingCommits { .. })
1197 );
1198 }
1199
1200 #[test]
1201 fn test_commit_quorum_forked_merge_commits() {
1202 let c0 = commit("f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354");
1203 let c1 = commit("bfb1a513e420eade90b0e6be64117b861b16ecb5");
1204 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
1205 let b2 = commit("037a148170e3d41524b7c482a4798e5c2daeaa00");
1206 let a1 = commit("2224468e22b30359611d880ccf0850d023f86f9b");
1207 let m1 = commit("dd7ee5bca2fc7288a6efcb4303278e26a2dbaa45");
1208 let m2 = commit("d54e505e3fb5c0c7e4b9a4b8a1cdeefb3fc9ef18");
1209
1210 let cq = CommitQuorum::new([m1].iter(), 1);
1218 assert_eq!(cq.find_quorum().unwrap(), m1.id());
1219
1220 let mut cq = CommitQuorum::new([m1, m2].iter(), 1);
1221 cq.found_merge_bases([MergeBase {
1222 a: m1.id(),
1223 b: m2.id(),
1224 base: b2.id(),
1225 }]);
1226 assert_matches!(
1227 cq.find_quorum(),
1228 Err(CommitQuorumFailure::DivergingCommits { .. })
1229 );
1230
1231 let mut cq = CommitQuorum::new([m2, m1].iter(), 1);
1232 cq.found_merge_bases([MergeBase {
1233 a: m2.id(),
1234 b: m1.id(),
1235 base: b2.id(),
1236 }]);
1237 assert_matches!(
1238 cq.find_quorum(),
1239 Err(CommitQuorumFailure::DivergingCommits { .. })
1240 );
1241
1242 let mut cq = CommitQuorum::new([m1, m2].iter(), 2);
1243 cq.found_merge_bases([MergeBase {
1244 a: m1.id(),
1245 b: m2.id(),
1246 base: b2.id(),
1247 }]);
1248 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
1249
1250 let mut cq = CommitQuorum::new([m1, m2, c2].iter(), 1);
1251 cq.found_merge_bases([
1252 MergeBase {
1253 a: m1.id(),
1254 b: m2.id(),
1255 base: b2.id(),
1256 },
1257 MergeBase {
1258 a: m1.id(),
1259 b: c2.id(),
1260 base: c2.id(),
1261 },
1262 MergeBase {
1263 a: m2.id(),
1264 b: c2.id(),
1265 base: c0.id(),
1266 },
1267 ]);
1268 assert_matches!(
1269 cq.find_quorum(),
1270 Err(CommitQuorumFailure::DivergingCommits { .. })
1271 );
1272
1273 let mut cq = CommitQuorum::new([m1, a1].iter(), 1);
1274 cq.found_merge_bases([MergeBase {
1275 a: m1.id(),
1276 b: a1.id(),
1277 base: c0.id(),
1278 }]);
1279 assert_matches!(
1280 cq.find_quorum(),
1281 Err(CommitQuorumFailure::DivergingCommits { .. })
1282 );
1283
1284 let mut cq = CommitQuorum::new([m1, a1].iter(), 2);
1285 cq.found_merge_bases([MergeBase {
1286 a: m1.id(),
1287 b: a1.id(),
1288 base: c0.id(),
1289 }]);
1290 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
1291
1292 let mut cq = CommitQuorum::new([m1, m2, b2, c1].iter(), 4);
1293 cq.found_merge_bases([
1294 MergeBase {
1295 a: m1.id(),
1296 b: m2.id(),
1297 base: b2.id(),
1298 },
1299 MergeBase {
1300 a: m1.id(),
1301 b: b2.id(),
1302 base: b2.id(),
1303 },
1304 MergeBase {
1305 a: m1.id(),
1306 b: c1.id(),
1307 base: c1.id(),
1308 },
1309 MergeBase {
1310 a: m2.id(),
1311 b: b2.id(),
1312 base: b2.id(),
1313 },
1314 MergeBase {
1315 a: m2.id(),
1316 b: c1.id(),
1317 base: c1.id(),
1318 },
1319 MergeBase {
1320 a: b2.id(),
1321 b: c1.id(),
1322 base: c1.id(),
1323 },
1324 ]);
1325 assert_eq!(cq.find_quorum().unwrap(), c1.id());
1326
1327 let mut cq = CommitQuorum::new([m1, m1, b2].iter(), 2);
1328 cq.found_merge_bases([
1329 MergeBase::trivial(m1.id()),
1330 MergeBase {
1331 a: m1.id(),
1332 b: b2.id(),
1333 base: b2.id(),
1334 },
1335 ]);
1336 assert_eq!(cq.find_quorum().unwrap(), m1.id());
1337
1338 let mut cq = CommitQuorum::new([m1, m1, c2].iter(), 2);
1339 cq.found_merge_bases([
1340 MergeBase::trivial(m1.id()),
1341 MergeBase {
1342 a: m1.id(),
1343 b: c2.id(),
1344 base: c2.id(),
1345 },
1346 ]);
1347 assert_eq!(cq.find_quorum().unwrap(), m1.id());
1348
1349 let mut cq = CommitQuorum::new([m2, m2, b2].iter(), 2);
1350 cq.found_merge_bases([
1351 MergeBase::trivial(m2.id()),
1352 MergeBase {
1353 a: m2.id(),
1354 b: b2.id(),
1355 base: b2.id(),
1356 },
1357 ]);
1358 assert_eq!(cq.find_quorum().unwrap(), m2.id());
1359
1360 let mut cq = CommitQuorum::new([m2, m2, a1].iter(), 2);
1361 cq.found_merge_bases([
1362 MergeBase::trivial(m2.id()),
1363 MergeBase {
1364 a: m2.id(),
1365 b: a1.id(),
1366 base: a1.id(),
1367 },
1368 ]);
1369 assert_eq!(cq.find_quorum().unwrap(), m2.id());
1370
1371 let mut cq = CommitQuorum::new([m1, m1, b2, b2].iter(), 2);
1372 cq.found_merge_bases([
1373 MergeBase::trivial(m1.id()),
1374 MergeBase::trivial(b2.id()),
1375 MergeBase {
1376 a: m1.id(),
1377 b: b2.id(),
1378 base: b2.id(),
1379 },
1380 ]);
1381 assert_eq!(cq.find_quorum().unwrap(), m1.id());
1382
1383 let mut cq = CommitQuorum::new([m1, m1, c2, c2].iter(), 2);
1384 cq.found_merge_bases([
1385 MergeBase::trivial(m1.id()),
1386 MergeBase::trivial(c2.id()),
1387 MergeBase {
1388 a: m1.id(),
1389 b: c2.id(),
1390 base: c2.id(),
1391 },
1392 ]);
1393 assert_eq!(cq.find_quorum().unwrap(), m1.id());
1394
1395 let mut cq = CommitQuorum::new([m1, b2, c1, c0].iter(), 3);
1396 cq.found_merge_bases([
1397 MergeBase {
1398 a: m1.id(),
1399 b: b2.id(),
1400 base: b2.id(),
1401 },
1402 MergeBase {
1403 a: m1.id(),
1404 b: c1.id(),
1405 base: c1.id(),
1406 },
1407 MergeBase {
1408 a: m1.id(),
1409 b: c0.id(),
1410 base: c0.id(),
1411 },
1412 MergeBase {
1413 a: b2.id(),
1414 b: c1.id(),
1415 base: c1.id(),
1416 },
1417 MergeBase {
1418 a: b2.id(),
1419 b: c0.id(),
1420 base: c0.id(),
1421 },
1422 MergeBase {
1423 a: c1.id(),
1424 b: c0.id(),
1425 base: c0.id(),
1426 },
1427 ]);
1428 assert_eq!(cq.find_quorum().unwrap(), c1.id());
1429
1430 let mut cq = CommitQuorum::new([m1, b2, c1, c0].iter(), 4);
1431 cq.found_merge_bases([
1432 MergeBase {
1433 a: m1.id(),
1434 b: b2.id(),
1435 base: b2.id(),
1436 },
1437 MergeBase {
1438 a: m1.id(),
1439 b: c1.id(),
1440 base: c1.id(),
1441 },
1442 MergeBase {
1443 a: m1.id(),
1444 b: c0.id(),
1445 base: c0.id(),
1446 },
1447 MergeBase {
1448 a: b2.id(),
1449 b: c1.id(),
1450 base: c0.id(),
1451 },
1452 MergeBase {
1453 a: b2.id(),
1454 b: c0.id(),
1455 base: c0.id(),
1456 },
1457 MergeBase {
1458 a: c1.id(),
1459 b: c0.id(),
1460 base: c0.id(),
1461 },
1462 ]);
1463 assert_eq!(cq.find_quorum().unwrap(), c0.id());
1464 }
1465
1466 #[test]
1467 fn test_commit_quorum_merges() {
1468 let c2 = commit("8fc5160702365f231c77732a8fa162379e54f57a");
1469 let m1 = commit("dd7ee5bca2fc7288a6efcb4303278e26a2dbaa45");
1470 let m2 = commit("d54e505e3fb5c0c7e4b9a4b8a1cdeefb3fc9ef18");
1471 let m3 = commit("2224468e22b30359611d880ccf0850d023f86f9b");
1472
1473 let mut cq = CommitQuorum::new([m1, m2].iter(), 1);
1480 cq.found_merge_bases([MergeBase {
1481 a: m1.id(),
1482 b: m2.id(),
1483 base: c2.id(),
1484 }]);
1485 assert_matches!(
1486 cq.find_quorum(),
1487 Err(CommitQuorumFailure::DivergingCommits { .. })
1488 );
1489
1490 let mut cq = CommitQuorum::new([m1, m2].iter(), 2);
1491 cq.found_merge_bases([MergeBase {
1492 a: m1.id(),
1493 b: m2.id(),
1494 base: c2.id(),
1495 }]);
1496 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
1497
1498 let mut cq = CommitQuorum::new([m1, m3].iter(), 1);
1504 cq.found_merge_bases([MergeBase {
1505 a: m1.id(),
1506 b: m3.id(),
1507 base: c2.id(),
1508 }]);
1509 assert_matches!(
1510 cq.find_quorum(),
1511 Err(CommitQuorumFailure::DivergingCommits { .. })
1512 );
1513
1514 let mut cq = CommitQuorum::new([m1, m3].iter(), 2);
1515 cq.found_merge_bases([MergeBase {
1516 a: m1.id(),
1517 b: m3.id(),
1518 base: c2.id(),
1519 }]);
1520 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
1521
1522 let mut cq = CommitQuorum::new([m3, m1].iter(), 1);
1523 cq.found_merge_bases([MergeBase {
1524 a: m3.id(),
1525 b: m1.id(),
1526 base: c2.id(),
1527 }]);
1528 assert_matches!(
1529 cq.find_quorum(),
1530 Err(CommitQuorumFailure::DivergingCommits { .. })
1531 );
1532
1533 let mut cq = CommitQuorum::new([m3, m1].iter(), 2);
1534 cq.found_merge_bases([MergeBase {
1535 a: m3.id(),
1536 b: m1.id(),
1537 base: c2.id(),
1538 }]);
1539 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
1540
1541 let mut cq = CommitQuorum::new([m3, m2].iter(), 1);
1542 cq.found_merge_bases([MergeBase {
1543 a: m3.id(),
1544 b: m2.id(),
1545 base: c2.id(),
1546 }]);
1547 assert_matches!(
1548 cq.find_quorum(),
1549 Err(CommitQuorumFailure::DivergingCommits { .. })
1550 );
1551
1552 let mut cq = CommitQuorum::new([m3, m2].iter(), 2);
1553 cq.found_merge_bases([MergeBase {
1554 a: m3.id(),
1555 b: m2.id(),
1556 base: c2.id(),
1557 }]);
1558 assert_eq!(cq.find_quorum(), Err(CommitQuorumFailure::NoCandidates));
1559 }
1560}