Skip to main content

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}