common/mount/conflict/types.rs
1//! Core types for conflict resolution
2//!
3//! This module defines the fundamental types used in conflict detection
4//! and resolution during PathOpLog merges.
5
6use std::path::PathBuf;
7
8use super::super::path_ops::PathOperation;
9
10/// A detected conflict between two operations on the same path
11#[derive(Debug, Clone)]
12pub struct Conflict {
13 /// The path where the conflict occurred
14 pub path: PathBuf,
15 /// The local (base) operation
16 pub base: PathOperation,
17 /// The incoming (remote) operation
18 pub incoming: PathOperation,
19}
20
21impl Conflict {
22 /// Create a new conflict
23 pub fn new(path: PathBuf, base: PathOperation, incoming: PathOperation) -> Self {
24 Self {
25 path,
26 base,
27 incoming,
28 }
29 }
30
31 /* Getters */
32
33 /// Check if both operations have the same timestamp (true concurrent edit)
34 pub fn is_concurrent(&self) -> bool {
35 self.base.id.timestamp == self.incoming.id.timestamp
36 }
37
38 /// Get the operation with the higher OpId (the "winner" by default CRDT rules)
39 pub fn crdt_winner(&self) -> &PathOperation {
40 if self.incoming.id > self.base.id {
41 &self.incoming
42 } else {
43 &self.base
44 }
45 }
46}
47
48/// Resolution decision for a conflict
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum Resolution {
51 /// Use the base (local) operation
52 UseBase,
53 /// Use the incoming (remote) operation
54 UseIncoming,
55 /// Keep both operations (fork state)
56 KeepBoth,
57 /// Skip both operations (neither is applied)
58 SkipBoth,
59 /// Rename the incoming operation to a new path (creates a conflict file)
60 RenameIncoming {
61 /// The new path for the incoming operation
62 new_path: PathBuf,
63 },
64}
65
66/// Result of a merge operation with conflict information
67#[derive(Debug, Clone)]
68pub struct MergeResult {
69 /// Number of operations added from the incoming log
70 pub operations_added: usize,
71 /// Conflicts that were resolved
72 pub conflicts_resolved: Vec<ResolvedConflict>,
73 /// Conflicts that could not be auto-resolved (when using ForkOnConflict)
74 pub unresolved_conflicts: Vec<Conflict>,
75}
76
77impl MergeResult {
78 /// Create a new merge result
79 pub fn new() -> Self {
80 Self {
81 operations_added: 0,
82 conflicts_resolved: Vec::new(),
83 unresolved_conflicts: Vec::new(),
84 }
85 }
86
87 /* Getters */
88
89 /// Check if there were any unresolved conflicts
90 pub fn has_unresolved(&self) -> bool {
91 !self.unresolved_conflicts.is_empty()
92 }
93
94 /// Total number of conflicts (resolved + unresolved)
95 pub fn total_conflicts(&self) -> usize {
96 self.conflicts_resolved.len() + self.unresolved_conflicts.len()
97 }
98}
99
100impl Default for MergeResult {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106/// A conflict that was resolved
107#[derive(Debug, Clone)]
108pub struct ResolvedConflict {
109 /// The original conflict
110 pub conflict: Conflict,
111 /// How it was resolved
112 pub resolution: Resolution,
113}