1use std::collections::{BTreeMap, HashMap, HashSet};
16use std::fmt::{Debug, Error, Formatter};
17
18use thiserror::Error;
19
20use crate::backend::{CommitId, Timestamp};
21use crate::content_hash::ContentHash;
22
23content_hash! {
24 #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
25 pub struct WorkspaceId(String);
26}
27
28impl Debug for WorkspaceId {
29 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
30 f.debug_tuple("WorkspaceId").field(&self.0).finish()
31 }
32}
33
34impl Default for WorkspaceId {
35 fn default() -> Self {
36 Self("default".to_string())
37 }
38}
39
40impl WorkspaceId {
41 pub fn new(value: String) -> Self {
42 Self(value)
43 }
44
45 pub fn as_str(&self) -> &str {
46 &self.0
47 }
48}
49
50content_hash! {
51 #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
52 pub struct ViewId(Vec<u8>);
53}
54
55impl Debug for ViewId {
56 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
57 f.debug_tuple("ViewId").field(&self.hex()).finish()
58 }
59}
60
61impl ViewId {
62 pub fn new(value: Vec<u8>) -> Self {
63 Self(value)
64 }
65
66 pub fn from_hex(hex: &str) -> Self {
67 Self(hex::decode(hex).unwrap())
68 }
69
70 pub fn as_bytes(&self) -> &[u8] {
71 &self.0
72 }
73
74 pub fn to_bytes(&self) -> Vec<u8> {
75 self.0.clone()
76 }
77
78 pub fn hex(&self) -> String {
79 hex::encode(&self.0)
80 }
81}
82
83content_hash! {
84 #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
85 pub struct OperationId(Vec<u8>);
86}
87
88impl Debug for OperationId {
89 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
90 f.debug_tuple("OperationId").field(&self.hex()).finish()
91 }
92}
93
94impl OperationId {
95 pub fn new(value: Vec<u8>) -> Self {
96 Self(value)
97 }
98
99 pub fn from_hex(hex: &str) -> Self {
100 Self(hex::decode(hex).unwrap())
101 }
102
103 pub fn as_bytes(&self) -> &[u8] {
104 &self.0
105 }
106
107 pub fn to_bytes(&self) -> Vec<u8> {
108 self.0.clone()
109 }
110
111 pub fn hex(&self) -> String {
112 hex::encode(&self.0)
113 }
114}
115
116#[derive(PartialEq, Eq, Clone, Debug)]
117pub enum RefTarget {
118 Normal(CommitId),
119 Conflict {
120 removes: Vec<CommitId>,
121 adds: Vec<CommitId>,
122 },
123}
124
125impl ContentHash for RefTarget {
126 fn hash(&self, state: &mut impl digest::Update) {
127 use RefTarget::*;
128 match self {
129 Normal(id) => {
130 state.update(&0u32.to_le_bytes());
131 id.hash(state);
132 }
133 Conflict { removes, adds } => {
134 state.update(&1u32.to_le_bytes());
135 removes.hash(state);
136 adds.hash(state);
137 }
138 }
139 }
140}
141
142impl RefTarget {
143 pub fn is_conflict(&self) -> bool {
144 matches!(self, RefTarget::Conflict { .. })
145 }
146
147 pub fn adds(&self) -> Vec<CommitId> {
148 match self {
149 RefTarget::Normal(id) => {
150 vec![id.clone()]
151 }
152 RefTarget::Conflict { removes: _, adds } => adds.clone(),
153 }
154 }
155
156 pub fn has_add(&self, needle: &CommitId) -> bool {
157 match self {
158 RefTarget::Normal(id) => id == needle,
159 RefTarget::Conflict { removes: _, adds } => adds.contains(needle),
160 }
161 }
162
163 pub fn removes(&self) -> Vec<CommitId> {
164 match self {
165 RefTarget::Normal(_) => {
166 vec![]
167 }
168 RefTarget::Conflict { removes, adds: _ } => removes.clone(),
169 }
170 }
171}
172
173content_hash! {
174 #[derive(Default, PartialEq, Eq, Clone, Debug)]
175 pub struct BranchTarget {
176 pub local_target: Option<RefTarget>,
179 pub remote_targets: BTreeMap<String, RefTarget>,
184 }
185}
186
187content_hash! {
188 #[derive(PartialEq, Eq, Clone, Debug, Default)]
191 pub struct View {
192 pub head_ids: HashSet<CommitId>,
194 pub public_head_ids: HashSet<CommitId>,
196 pub branches: BTreeMap<String, BranchTarget>,
197 pub tags: BTreeMap<String, RefTarget>,
198 pub git_refs: BTreeMap<String, RefTarget>,
199 pub git_head: Option<RefTarget>,
203 pub wc_commit_ids: HashMap<WorkspaceId, CommitId>,
207 }
208}
209
210content_hash! {
211 #[derive(PartialEq, Eq, Clone, Debug)]
224 pub struct Operation {
225 pub view_id: ViewId,
226 pub parents: Vec<OperationId>,
227 pub metadata: OperationMetadata,
228 }
229}
230
231content_hash! {
232 #[derive(PartialEq, Eq, Clone, Debug)]
233 pub struct OperationMetadata {
234 pub start_time: Timestamp,
235 pub end_time: Timestamp,
236 pub description: String,
238 pub hostname: String,
239 pub username: String,
240 pub tags: HashMap<String, String>,
241 }
242}
243
244#[derive(Debug, Error)]
245pub enum OpStoreError {
246 #[error("Operation not found")]
247 NotFound,
248 #[error("{0}")]
249 Other(String),
250}
251
252pub type OpStoreResult<T> = Result<T, OpStoreError>;
253
254pub trait OpStore: Send + Sync + Debug {
255 fn name(&self) -> &str;
256
257 fn read_view(&self, id: &ViewId) -> OpStoreResult<View>;
258
259 fn write_view(&self, contents: &View) -> OpStoreResult<ViewId>;
260
261 fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation>;
262
263 fn write_operation(&self, contents: &Operation) -> OpStoreResult<OperationId>;
264}