1#![allow(missing_docs)]
16
17use std::any::Any;
18use std::collections::BTreeMap;
19use std::fmt::Debug;
20use std::io::Read;
21use std::time::SystemTime;
22
23use async_trait::async_trait;
24use futures::stream::BoxStream;
25use thiserror::Error;
26
27use crate::content_hash::ContentHash;
28use crate::hex_util;
29use crate::index::Index;
30use crate::merge::Merge;
31use crate::object_id::id_type;
32use crate::object_id::ObjectId as _;
33use crate::repo_path::RepoPath;
34use crate::repo_path::RepoPathBuf;
35use crate::repo_path::RepoPathComponent;
36use crate::repo_path::RepoPathComponentBuf;
37use crate::signing::SignResult;
38
39id_type!(
40 pub CommitId { hex() }
43);
44id_type!(
45 pub ChangeId { reverse_hex() }
48);
49id_type!(pub TreeId { hex() });
50id_type!(pub FileId { hex() });
51id_type!(pub SymlinkId { hex() });
52id_type!(pub ConflictId { hex() });
53
54impl ChangeId {
55 pub fn reverse_hex(&self) -> String {
58 hex_util::encode_reverse_hex(&self.0)
59 }
60}
61
62#[derive(ContentHash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
63pub struct MillisSinceEpoch(pub i64);
64
65#[derive(ContentHash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
66pub struct Timestamp {
67 pub timestamp: MillisSinceEpoch,
68 pub tz_offset: i32,
70}
71
72impl Timestamp {
73 pub fn now() -> Self {
74 Self::from_datetime(chrono::offset::Local::now())
75 }
76
77 pub fn from_datetime<Tz: chrono::TimeZone<Offset = chrono::offset::FixedOffset>>(
78 datetime: chrono::DateTime<Tz>,
79 ) -> Self {
80 Self {
81 timestamp: MillisSinceEpoch(datetime.timestamp_millis()),
82 tz_offset: datetime.offset().local_minus_utc() / 60,
83 }
84 }
85}
86
87#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
89pub struct Signature {
90 pub name: String,
91 pub email: String,
92 pub timestamp: Timestamp,
93}
94
95#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
97pub struct SecureSig {
98 pub data: Vec<u8>,
99 pub sig: Vec<u8>,
100}
101
102pub type SigningFn<'a> = dyn FnMut(&[u8]) -> SignResult<Vec<u8>> + Send + 'a;
103
104#[derive(ContentHash, Debug, Clone)]
110pub enum MergedTreeId {
111 Legacy(TreeId),
113 Merge(Merge<TreeId>),
115}
116
117impl PartialEq for MergedTreeId {
118 fn eq(&self, other: &Self) -> bool {
121 self.to_merge() == other.to_merge()
122 }
123}
124
125impl Eq for MergedTreeId {}
126
127impl MergedTreeId {
128 pub fn resolved(tree_id: TreeId) -> Self {
130 MergedTreeId::Merge(Merge::resolved(tree_id))
131 }
132
133 pub fn to_merge(&self) -> Merge<TreeId> {
135 match self {
136 MergedTreeId::Legacy(tree_id) => Merge::resolved(tree_id.clone()),
137 MergedTreeId::Merge(tree_ids) => tree_ids.clone(),
138 }
139 }
140}
141
142#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
143pub struct Commit {
144 pub parents: Vec<CommitId>,
145 pub predecessors: Vec<CommitId>,
146 pub root_tree: MergedTreeId,
147 pub change_id: ChangeId,
148 pub description: String,
149 pub author: Signature,
150 pub committer: Signature,
151 pub secure_sig: Option<SecureSig>,
152}
153
154#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
155pub struct ConflictTerm {
156 pub value: TreeValue,
157}
158
159#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
160pub struct Conflict {
161 pub removes: Vec<ConflictTerm>,
166 pub adds: Vec<ConflictTerm>,
167}
168
169#[derive(Debug, PartialEq, Eq, Clone)]
171pub struct CopyRecord {
172 pub target: RepoPathBuf,
174 pub target_commit: CommitId,
176 pub source: RepoPathBuf,
182 pub source_file: FileId,
183 pub source_commit: CommitId,
194}
195
196#[derive(Debug, Error)]
198#[error(transparent)]
199pub struct BackendInitError(pub Box<dyn std::error::Error + Send + Sync>);
200
201#[derive(Debug, Error)]
203#[error(transparent)]
204pub struct BackendLoadError(pub Box<dyn std::error::Error + Send + Sync>);
205
206#[derive(Debug, Error)]
208pub enum BackendError {
209 #[error(
210 "Invalid hash length for object of type {object_type} (expected {expected} bytes, got \
211 {actual} bytes): {hash}"
212 )]
213 InvalidHashLength {
214 expected: usize,
215 actual: usize,
216 object_type: String,
217 hash: String,
218 },
219 #[error("Invalid UTF-8 for object {hash} of type {object_type}")]
220 InvalidUtf8 {
221 object_type: String,
222 hash: String,
223 source: std::str::Utf8Error,
224 },
225 #[error("Object {hash} of type {object_type} not found")]
226 ObjectNotFound {
227 object_type: String,
228 hash: String,
229 source: Box<dyn std::error::Error + Send + Sync>,
230 },
231 #[error("Error when reading object {hash} of type {object_type}")]
232 ReadObject {
233 object_type: String,
234 hash: String,
235 source: Box<dyn std::error::Error + Send + Sync>,
236 },
237 #[error("Access denied to read object {hash} of type {object_type}")]
238 ReadAccessDenied {
239 object_type: String,
240 hash: String,
241 source: Box<dyn std::error::Error + Send + Sync>,
242 },
243 #[error(
244 "Error when reading file content for file {path} with id {id}",
245 path = path.as_internal_file_string()
246 )]
247 ReadFile {
248 path: RepoPathBuf,
249 id: FileId,
250 source: Box<dyn std::error::Error + Send + Sync>,
251 },
252 #[error("Could not write object of type {object_type}")]
253 WriteObject {
254 object_type: &'static str,
255 source: Box<dyn std::error::Error + Send + Sync>,
256 },
257 #[error(transparent)]
258 Other(Box<dyn std::error::Error + Send + Sync>),
259 #[error("{0}")]
262 Unsupported(String),
263}
264
265pub type BackendResult<T> = Result<T, BackendError>;
266
267#[derive(ContentHash, Debug, PartialEq, Eq, Clone, Hash)]
268pub enum TreeValue {
269 File { id: FileId, executable: bool },
270 Symlink(SymlinkId),
271 Tree(TreeId),
272 GitSubmodule(CommitId),
273 Conflict(ConflictId),
274}
275
276impl TreeValue {
277 pub fn hex(&self) -> String {
278 match self {
279 TreeValue::File { id, .. } => id.hex(),
280 TreeValue::Symlink(id) => id.hex(),
281 TreeValue::Tree(id) => id.hex(),
282 TreeValue::GitSubmodule(id) => id.hex(),
283 TreeValue::Conflict(id) => id.hex(),
284 }
285 }
286}
287
288#[derive(Debug, PartialEq, Eq, Clone)]
289pub struct TreeEntry<'a> {
290 name: &'a RepoPathComponent,
291 value: &'a TreeValue,
292}
293
294impl<'a> TreeEntry<'a> {
295 pub fn new(name: &'a RepoPathComponent, value: &'a TreeValue) -> Self {
296 TreeEntry { name, value }
297 }
298
299 pub fn name(&self) -> &'a RepoPathComponent {
300 self.name
301 }
302
303 pub fn value(&self) -> &'a TreeValue {
304 self.value
305 }
306}
307
308pub struct TreeEntriesNonRecursiveIterator<'a> {
309 iter: std::collections::btree_map::Iter<'a, RepoPathComponentBuf, TreeValue>,
310}
311
312impl<'a> Iterator for TreeEntriesNonRecursiveIterator<'a> {
313 type Item = TreeEntry<'a>;
314
315 fn next(&mut self) -> Option<Self::Item> {
316 self.iter
317 .next()
318 .map(|(name, value)| TreeEntry { name, value })
319 }
320}
321
322#[derive(ContentHash, Default, PartialEq, Eq, Debug, Clone)]
323pub struct Tree {
324 entries: BTreeMap<RepoPathComponentBuf, TreeValue>,
325}
326
327impl Tree {
328 pub fn is_empty(&self) -> bool {
329 self.entries.is_empty()
330 }
331
332 pub fn names(&self) -> impl Iterator<Item = &RepoPathComponent> {
333 self.entries.keys().map(|name| name.as_ref())
334 }
335
336 pub fn entries(&self) -> TreeEntriesNonRecursiveIterator {
337 TreeEntriesNonRecursiveIterator {
338 iter: self.entries.iter(),
339 }
340 }
341
342 pub fn set(&mut self, name: RepoPathComponentBuf, value: TreeValue) {
343 self.entries.insert(name, value);
344 }
345
346 pub fn remove(&mut self, name: &RepoPathComponent) {
347 self.entries.remove(name);
348 }
349
350 pub fn set_or_remove(&mut self, name: &RepoPathComponent, value: Option<TreeValue>) {
351 match value {
352 None => {
353 self.entries.remove(name);
354 }
355 Some(value) => {
356 self.entries.insert(name.to_owned(), value);
357 }
358 }
359 }
360
361 pub fn entry(&self, name: &RepoPathComponent) -> Option<TreeEntry> {
362 self.entries
363 .get_key_value(name)
364 .map(|(name, value)| TreeEntry { name, value })
365 }
366
367 pub fn value(&self, name: &RepoPathComponent) -> Option<&TreeValue> {
368 self.entries.get(name)
369 }
370}
371
372pub fn make_root_commit(root_change_id: ChangeId, empty_tree_id: TreeId) -> Commit {
373 let timestamp = Timestamp {
374 timestamp: MillisSinceEpoch(0),
375 tz_offset: 0,
376 };
377 let signature = Signature {
378 name: String::new(),
379 email: String::new(),
380 timestamp,
381 };
382 Commit {
383 parents: vec![],
384 predecessors: vec![],
385 root_tree: MergedTreeId::resolved(empty_tree_id),
386 change_id: root_change_id,
387 description: String::new(),
388 author: signature.clone(),
389 committer: signature,
390 secure_sig: None,
391 }
392}
393
394#[async_trait]
396pub trait Backend: Send + Sync + Debug {
397 fn as_any(&self) -> &dyn Any;
398
399 fn name(&self) -> &str;
402
403 fn commit_id_length(&self) -> usize;
405
406 fn change_id_length(&self) -> usize;
408
409 fn root_commit_id(&self) -> &CommitId;
410
411 fn root_change_id(&self) -> &ChangeId;
412
413 fn empty_tree_id(&self) -> &TreeId;
414
415 fn concurrency(&self) -> usize;
423
424 async fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>>;
425
426 async fn write_file(
427 &self,
428 path: &RepoPath,
429 contents: &mut (dyn Read + Send),
430 ) -> BackendResult<FileId>;
431
432 async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String>;
433
434 async fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult<SymlinkId>;
435
436 async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree>;
437
438 async fn write_tree(&self, path: &RepoPath, contents: &Tree) -> BackendResult<TreeId>;
439
440 fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict>;
443
444 fn write_conflict(&self, path: &RepoPath, contents: &Conflict) -> BackendResult<ConflictId>;
445
446 async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit>;
447
448 async fn write_commit(
462 &self,
463 contents: Commit,
464 sign_with: Option<&mut SigningFn>,
465 ) -> BackendResult<(CommitId, Commit)>;
466
467 fn get_copy_records(
480 &self,
481 paths: Option<&[RepoPathBuf]>,
482 root: &CommitId,
483 head: &CommitId,
484 ) -> BackendResult<BoxStream<BackendResult<CopyRecord>>>;
485
486 fn gc(&self, index: &dyn Index, keep_newer: SystemTime) -> BackendResult<()>;
492}