use std::collections::{HashMap, HashSet};
pub type ManifestMap = HashMap<String, [u8; 16]>;
#[derive(Debug, Clone, PartialEq)]
pub struct Conflict {
pub path: String,
pub base_hash: Option<[u8; 16]>,
pub local_hash: Option<[u8; 16]>,
pub remote_hash: Option<[u8; 16]>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Resolution {
KeepLocal,
KeepRemote,
Merged(Vec<u8>),
Skip,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FileAction {
ApplyRemote { path: String, remote_hash: [u8; 16] },
DeleteLocal { path: String },
Conflict(Conflict),
}
pub trait ConflictResolver {
fn resolve(&self, conflict: &Conflict) -> Resolution;
}
pub struct KeepLocalResolver;
impl ConflictResolver for KeepLocalResolver {
fn resolve(&self, _conflict: &Conflict) -> Resolution {
Resolution::KeepLocal
}
}
pub fn detect_conflicts(
base: &ManifestMap,
local: &ManifestMap,
remote: &ManifestMap,
) -> Vec<FileAction> {
let mut actions = Vec::new();
let all_paths: HashSet<&String> = base
.keys()
.chain(local.keys())
.chain(remote.keys())
.collect();
for path in all_paths {
let b = base.get(path);
let l = local.get(path);
let r = remote.get(path);
let action = match (b, l, r) {
(Some(bh), Some(lh), Some(rh)) if bh == lh && lh == rh => None,
(Some(bh), Some(lh), Some(rh)) if bh == lh && lh != rh => {
Some(FileAction::ApplyRemote {
path: path.clone(),
remote_hash: *rh,
})
}
(Some(bh), Some(_lh), Some(rh)) if bh == rh => None,
(Some(_bh), Some(lh), Some(rh)) if lh == rh => None,
(Some(bh), Some(lh), Some(rh)) => Some(FileAction::Conflict(Conflict {
path: path.clone(),
base_hash: Some(*bh),
local_hash: Some(*lh),
remote_hash: Some(*rh),
})),
(None, Some(_lh), None) => None,
(None, None, Some(rh)) => Some(FileAction::ApplyRemote {
path: path.clone(),
remote_hash: *rh,
}),
(None, Some(lh), Some(rh)) if lh != rh => Some(FileAction::Conflict(Conflict {
path: path.clone(),
base_hash: None,
local_hash: Some(*lh),
remote_hash: Some(*rh),
})),
(None, Some(_lh), Some(_rh)) => None,
(Some(bh), None, Some(rh)) if bh == rh => None,
(Some(bh), None, Some(rh)) => Some(FileAction::Conflict(Conflict {
path: path.clone(),
base_hash: Some(*bh),
local_hash: None,
remote_hash: Some(*rh),
})),
(Some(bh), Some(lh), None) if bh == lh => {
Some(FileAction::DeleteLocal { path: path.clone() })
}
(Some(bh), Some(lh), None) => Some(FileAction::Conflict(Conflict {
path: path.clone(),
base_hash: Some(*bh),
local_hash: Some(*lh),
remote_hash: None,
})),
(Some(_bh), None, None) => None,
(None, None, None) => None,
};
if let Some(a) = action {
actions.push(a);
}
}
actions
}