#![allow(missing_docs)]
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::{Debug, Error, Formatter};
use std::slice;
use thiserror::Error;
use crate::backend::{CommitId, Timestamp};
use crate::content_hash::ContentHash;
content_hash! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct WorkspaceId(String);
}
impl Debug for WorkspaceId {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_tuple("WorkspaceId").field(&self.0).finish()
}
}
impl Default for WorkspaceId {
fn default() -> Self {
Self("default".to_string())
}
}
impl WorkspaceId {
pub fn new(value: String) -> Self {
Self(value)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
content_hash! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct ViewId(Vec<u8>);
}
impl Debug for ViewId {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_tuple("ViewId").field(&self.hex()).finish()
}
}
impl ViewId {
pub fn new(value: Vec<u8>) -> Self {
Self(value)
}
pub fn from_hex(hex: &str) -> Self {
Self(hex::decode(hex).unwrap())
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.clone()
}
pub fn hex(&self) -> String {
hex::encode(&self.0)
}
}
content_hash! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct OperationId(Vec<u8>);
}
impl Debug for OperationId {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_tuple("OperationId").field(&self.hex()).finish()
}
}
impl OperationId {
pub fn new(value: Vec<u8>) -> Self {
Self(value)
}
pub fn from_hex(hex: &str) -> Self {
Self(hex::decode(hex).unwrap())
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.clone()
}
pub fn hex(&self) -> String {
hex::encode(&self.0)
}
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum RefTarget {
Normal(CommitId),
Conflict {
removes: Vec<CommitId>,
adds: Vec<CommitId>,
},
}
impl ContentHash for RefTarget {
fn hash(&self, state: &mut impl digest::Update) {
use RefTarget::*;
match self {
Normal(id) => {
state.update(&0u32.to_le_bytes());
id.hash(state);
}
Conflict { removes, adds } => {
state.update(&1u32.to_le_bytes());
removes.hash(state);
adds.hash(state);
}
}
}
}
impl RefTarget {
pub fn normal(id: CommitId) -> Option<Self> {
Some(RefTarget::Normal(id))
}
pub fn from_legacy_form(
removed_ids: impl IntoIterator<Item = CommitId>,
added_ids: impl IntoIterator<Item = CommitId>,
) -> Option<Self> {
let removes = removed_ids.into_iter().collect();
let adds = added_ids.into_iter().collect();
Some(RefTarget::Conflict { removes, adds })
}
pub fn as_normal(&self) -> Option<&CommitId> {
match self {
RefTarget::Normal(id) => Some(id),
RefTarget::Conflict { .. } => None,
}
}
pub fn is_conflict(&self) -> bool {
matches!(self, RefTarget::Conflict { .. })
}
pub fn removes(&self) -> &[CommitId] {
match self {
RefTarget::Normal(_) => &[],
RefTarget::Conflict { removes, adds: _ } => removes,
}
}
pub fn adds(&self) -> &[CommitId] {
match self {
RefTarget::Normal(id) => slice::from_ref(id),
RefTarget::Conflict { removes: _, adds } => adds,
}
}
}
pub trait RefTargetExt {
fn as_normal(&self) -> Option<&CommitId>;
fn is_absent(&self) -> bool;
fn is_present(&self) -> bool;
fn is_conflict(&self) -> bool;
fn removes(&self) -> &[CommitId];
fn adds(&self) -> &[CommitId];
}
impl RefTargetExt for Option<RefTarget> {
fn as_normal(&self) -> Option<&CommitId> {
self.as_ref().and_then(|target| target.as_normal())
}
fn is_absent(&self) -> bool {
self.is_none()
}
fn is_present(&self) -> bool {
self.is_some()
}
fn is_conflict(&self) -> bool {
self.as_ref()
.map(|target| target.is_conflict())
.unwrap_or(false)
}
fn removes(&self) -> &[CommitId] {
self.as_ref()
.map(|target| target.removes())
.unwrap_or_default()
}
fn adds(&self) -> &[CommitId] {
self.as_ref()
.map(|target| target.adds())
.unwrap_or_default()
}
}
impl RefTargetExt for Option<&RefTarget> {
fn as_normal(&self) -> Option<&CommitId> {
self.and_then(|target| target.as_normal())
}
fn is_absent(&self) -> bool {
self.is_none()
}
fn is_present(&self) -> bool {
self.is_some()
}
fn is_conflict(&self) -> bool {
self.map(|target| target.is_conflict()).unwrap_or(false)
}
fn removes(&self) -> &[CommitId] {
self.map(|target| target.removes()).unwrap_or_default()
}
fn adds(&self) -> &[CommitId] {
self.map(|target| target.adds()).unwrap_or_default()
}
}
content_hash! {
#[derive(Default, PartialEq, Eq, Clone, Debug)]
pub struct BranchTarget {
pub local_target: Option<RefTarget>,
pub remote_targets: BTreeMap<String, RefTarget>,
}
}
content_hash! {
#[derive(PartialEq, Eq, Clone, Debug, Default)]
pub struct View {
pub head_ids: HashSet<CommitId>,
pub public_head_ids: HashSet<CommitId>,
pub branches: BTreeMap<String, BranchTarget>,
pub tags: BTreeMap<String, RefTarget>,
pub git_refs: BTreeMap<String, RefTarget>,
pub git_head: Option<RefTarget>,
pub wc_commit_ids: HashMap<WorkspaceId, CommitId>,
}
}
content_hash! {
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Operation {
pub view_id: ViewId,
pub parents: Vec<OperationId>,
pub metadata: OperationMetadata,
}
}
content_hash! {
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct OperationMetadata {
pub start_time: Timestamp,
pub end_time: Timestamp,
pub description: String,
pub hostname: String,
pub username: String,
pub tags: HashMap<String, String>,
}
}
#[derive(Debug, Error)]
pub enum OpStoreError {
#[error("Operation not found")]
NotFound,
#[error("{0}")]
Other(String),
}
pub type OpStoreResult<T> = Result<T, OpStoreError>;
pub trait OpStore: Send + Sync + Debug {
fn name(&self) -> &str;
fn read_view(&self, id: &ViewId) -> OpStoreResult<View>;
fn write_view(&self, contents: &View) -> OpStoreResult<ViewId>;
fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation>;
fn write_operation(&self, contents: &Operation) -> OpStoreResult<OperationId>;
}