1#![allow(missing_docs)]
16
17use std::any::Any;
18use std::collections::BTreeMap;
19use std::fmt::Debug;
20use std::pin::Pin;
21use std::time::SystemTime;
22
23use async_trait::async_trait;
24use futures::stream::BoxStream;
25use thiserror::Error;
26use tokio::io::AsyncRead;
27
28use crate::content_hash::ContentHash;
29use crate::hex_util;
30use crate::index::Index;
31use crate::merge::Merge;
32use crate::object_id::id_type;
33use crate::object_id::ObjectId as _;
34use crate::repo_path::RepoPath;
35use crate::repo_path::RepoPathBuf;
36use crate::repo_path::RepoPathComponent;
37use crate::repo_path::RepoPathComponentBuf;
38use crate::signing::SignResult;
39
40id_type!(
41 pub CommitId { hex() }
44);
45id_type!(
46 pub ChangeId { reverse_hex() }
49);
50id_type!(pub TreeId { hex() });
51id_type!(pub FileId { hex() });
52id_type!(pub SymlinkId { hex() });
53id_type!(pub ConflictId { hex() });
54id_type!(pub CopyId { hex() });
55
56impl ChangeId {
57 pub fn reverse_hex(&self) -> String {
60 hex_util::encode_reverse_hex(&self.0)
61 }
62}
63
64impl CopyId {
65 pub fn placeholder() -> Self {
69 Self::new(vec![])
70 }
71}
72
73#[derive(ContentHash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
74pub struct MillisSinceEpoch(pub i64);
75
76#[derive(ContentHash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
77pub struct Timestamp {
78 pub timestamp: MillisSinceEpoch,
79 pub tz_offset: i32,
81}
82
83impl Timestamp {
84 pub fn now() -> Self {
85 Self::from_datetime(chrono::offset::Local::now())
86 }
87
88 pub fn from_datetime<Tz: chrono::TimeZone<Offset = chrono::offset::FixedOffset>>(
89 datetime: chrono::DateTime<Tz>,
90 ) -> Self {
91 Self {
92 timestamp: MillisSinceEpoch(datetime.timestamp_millis()),
93 tz_offset: datetime.offset().local_minus_utc() / 60,
94 }
95 }
96}
97
98#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
100pub struct Signature {
101 pub name: String,
102 pub email: String,
103 pub timestamp: Timestamp,
104}
105
106#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
108pub struct SecureSig {
109 pub data: Vec<u8>,
110 pub sig: Vec<u8>,
111}
112
113pub type SigningFn<'a> = dyn FnMut(&[u8]) -> SignResult<Vec<u8>> + Send + 'a;
114
115#[derive(ContentHash, Debug, Clone)]
121pub enum MergedTreeId {
122 Legacy(TreeId),
124 Merge(Merge<TreeId>),
126}
127
128impl PartialEq for MergedTreeId {
129 fn eq(&self, other: &Self) -> bool {
132 self.to_merge() == other.to_merge()
133 }
134}
135
136impl Eq for MergedTreeId {}
137
138impl MergedTreeId {
139 pub fn resolved(tree_id: TreeId) -> Self {
141 MergedTreeId::Merge(Merge::resolved(tree_id))
142 }
143
144 pub fn to_merge(&self) -> Merge<TreeId> {
146 match self {
147 MergedTreeId::Legacy(tree_id) => Merge::resolved(tree_id.clone()),
148 MergedTreeId::Merge(tree_ids) => tree_ids.clone(),
149 }
150 }
151}
152
153#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
154pub struct Commit {
155 pub parents: Vec<CommitId>,
156 pub predecessors: Vec<CommitId>,
159 pub root_tree: MergedTreeId,
160 pub change_id: ChangeId,
161 pub description: String,
162 pub author: Signature,
163 pub committer: Signature,
164 pub secure_sig: Option<SecureSig>,
165}
166
167#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
168pub struct ConflictTerm {
169 pub value: TreeValue,
170}
171
172#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
173pub struct Conflict {
174 pub removes: Vec<ConflictTerm>,
179 pub adds: Vec<ConflictTerm>,
180}
181
182#[derive(Debug, PartialEq, Eq, Clone)]
184pub struct CopyRecord {
185 pub target: RepoPathBuf,
187 pub target_commit: CommitId,
189 pub source: RepoPathBuf,
195 pub source_file: FileId,
196 pub source_commit: CommitId,
207}
208
209#[derive(ContentHash, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
212pub struct CopyHistory {
213 pub current_path: RepoPathBuf,
215 pub parents: Vec<CopyId>,
220 pub salt: Vec<u8>,
225}
226
227#[derive(Debug, Error)]
229#[error(transparent)]
230pub struct BackendInitError(pub Box<dyn std::error::Error + Send + Sync>);
231
232#[derive(Debug, Error)]
234#[error(transparent)]
235pub struct BackendLoadError(pub Box<dyn std::error::Error + Send + Sync>);
236
237#[derive(Debug, Error)]
239pub enum BackendError {
240 #[error(
241 "Invalid hash length for object of type {object_type} (expected {expected} bytes, got \
242 {actual} bytes): {hash}"
243 )]
244 InvalidHashLength {
245 expected: usize,
246 actual: usize,
247 object_type: String,
248 hash: String,
249 },
250 #[error("Invalid UTF-8 for object {hash} of type {object_type}")]
251 InvalidUtf8 {
252 object_type: String,
253 hash: String,
254 source: std::str::Utf8Error,
255 },
256 #[error("Object {hash} of type {object_type} not found")]
257 ObjectNotFound {
258 object_type: String,
259 hash: String,
260 source: Box<dyn std::error::Error + Send + Sync>,
261 },
262 #[error("Error when reading object {hash} of type {object_type}")]
263 ReadObject {
264 object_type: String,
265 hash: String,
266 source: Box<dyn std::error::Error + Send + Sync>,
267 },
268 #[error("Access denied to read object {hash} of type {object_type}")]
269 ReadAccessDenied {
270 object_type: String,
271 hash: String,
272 source: Box<dyn std::error::Error + Send + Sync>,
273 },
274 #[error(
275 "Error when reading file content for file {path} with id {id}",
276 path = path.as_internal_file_string()
277 )]
278 ReadFile {
279 path: RepoPathBuf,
280 id: FileId,
281 source: Box<dyn std::error::Error + Send + Sync>,
282 },
283 #[error("Could not write object of type {object_type}")]
284 WriteObject {
285 object_type: &'static str,
286 source: Box<dyn std::error::Error + Send + Sync>,
287 },
288 #[error(transparent)]
289 Other(Box<dyn std::error::Error + Send + Sync>),
290 #[error("{0}")]
293 Unsupported(String),
294}
295
296pub type BackendResult<T> = Result<T, BackendError>;
297
298#[derive(ContentHash, Debug, PartialEq, Eq, Clone, Hash)]
299pub enum TreeValue {
300 File {
303 id: FileId,
304 executable: bool,
305 copy_id: CopyId,
306 },
307 Symlink(SymlinkId),
308 Tree(TreeId),
309 GitSubmodule(CommitId),
310 Conflict(ConflictId),
311}
312
313impl TreeValue {
314 pub fn hex(&self) -> String {
315 match self {
316 TreeValue::File { id, .. } => id.hex(),
317 TreeValue::Symlink(id) => id.hex(),
318 TreeValue::Tree(id) => id.hex(),
319 TreeValue::GitSubmodule(id) => id.hex(),
320 TreeValue::Conflict(id) => id.hex(),
321 }
322 }
323}
324
325#[derive(Debug, PartialEq, Eq, Clone)]
326pub struct TreeEntry<'a> {
327 name: &'a RepoPathComponent,
328 value: &'a TreeValue,
329}
330
331impl<'a> TreeEntry<'a> {
332 pub fn new(name: &'a RepoPathComponent, value: &'a TreeValue) -> Self {
333 TreeEntry { name, value }
334 }
335
336 pub fn name(&self) -> &'a RepoPathComponent {
337 self.name
338 }
339
340 pub fn value(&self) -> &'a TreeValue {
341 self.value
342 }
343}
344
345pub struct TreeEntriesNonRecursiveIterator<'a> {
346 iter: std::collections::btree_map::Iter<'a, RepoPathComponentBuf, TreeValue>,
347}
348
349impl<'a> Iterator for TreeEntriesNonRecursiveIterator<'a> {
350 type Item = TreeEntry<'a>;
351
352 fn next(&mut self) -> Option<Self::Item> {
353 self.iter
354 .next()
355 .map(|(name, value)| TreeEntry { name, value })
356 }
357}
358
359#[derive(ContentHash, Default, PartialEq, Eq, Debug, Clone)]
360pub struct Tree {
361 entries: BTreeMap<RepoPathComponentBuf, TreeValue>,
362}
363
364impl Tree {
365 pub fn is_empty(&self) -> bool {
366 self.entries.is_empty()
367 }
368
369 pub fn names(&self) -> impl Iterator<Item = &RepoPathComponent> {
370 self.entries.keys().map(|name| name.as_ref())
371 }
372
373 pub fn entries(&self) -> TreeEntriesNonRecursiveIterator {
374 TreeEntriesNonRecursiveIterator {
375 iter: self.entries.iter(),
376 }
377 }
378
379 pub fn set(&mut self, name: RepoPathComponentBuf, value: TreeValue) {
380 self.entries.insert(name, value);
381 }
382
383 pub fn remove(&mut self, name: &RepoPathComponent) {
384 self.entries.remove(name);
385 }
386
387 pub fn set_or_remove(&mut self, name: &RepoPathComponent, value: Option<TreeValue>) {
388 match value {
389 None => {
390 self.entries.remove(name);
391 }
392 Some(value) => {
393 self.entries.insert(name.to_owned(), value);
394 }
395 }
396 }
397
398 pub fn entry(&self, name: &RepoPathComponent) -> Option<TreeEntry> {
399 self.entries
400 .get_key_value(name)
401 .map(|(name, value)| TreeEntry { name, value })
402 }
403
404 pub fn value(&self, name: &RepoPathComponent) -> Option<&TreeValue> {
405 self.entries.get(name)
406 }
407}
408
409pub fn make_root_commit(root_change_id: ChangeId, empty_tree_id: TreeId) -> Commit {
410 let timestamp = Timestamp {
411 timestamp: MillisSinceEpoch(0),
412 tz_offset: 0,
413 };
414 let signature = Signature {
415 name: String::new(),
416 email: String::new(),
417 timestamp,
418 };
419 Commit {
420 parents: vec![],
421 predecessors: vec![],
422 root_tree: MergedTreeId::resolved(empty_tree_id),
423 change_id: root_change_id,
424 description: String::new(),
425 author: signature.clone(),
426 committer: signature,
427 secure_sig: None,
428 }
429}
430
431#[async_trait]
433pub trait Backend: Send + Sync + Debug {
434 fn as_any(&self) -> &dyn Any;
435
436 fn name(&self) -> &str;
439
440 fn commit_id_length(&self) -> usize;
442
443 fn change_id_length(&self) -> usize;
445
446 fn root_commit_id(&self) -> &CommitId;
447
448 fn root_change_id(&self) -> &ChangeId;
449
450 fn empty_tree_id(&self) -> &TreeId;
451
452 fn concurrency(&self) -> usize;
460
461 async fn read_file(
462 &self,
463 path: &RepoPath,
464 id: &FileId,
465 ) -> BackendResult<Pin<Box<dyn AsyncRead>>>;
466
467 async fn write_file(
468 &self,
469 path: &RepoPath,
470 contents: &mut (dyn AsyncRead + Send + Unpin),
471 ) -> BackendResult<FileId>;
472
473 async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String>;
474
475 async fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult<SymlinkId>;
476
477 async fn read_copy(&self, id: &CopyId) -> BackendResult<CopyHistory>;
482
483 async fn write_copy(&self, copy: &CopyHistory) -> BackendResult<CopyId>;
488
489 async fn get_related_copies(&self, copy_id: &CopyId) -> BackendResult<Vec<CopyHistory>>;
499
500 async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree>;
501
502 async fn write_tree(&self, path: &RepoPath, contents: &Tree) -> BackendResult<TreeId>;
503
504 fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict>;
507
508 fn write_conflict(&self, path: &RepoPath, contents: &Conflict) -> BackendResult<ConflictId>;
509
510 async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit>;
511
512 async fn write_commit(
526 &self,
527 contents: Commit,
528 sign_with: Option<&mut SigningFn>,
529 ) -> BackendResult<(CommitId, Commit)>;
530
531 fn get_copy_records(
544 &self,
545 paths: Option<&[RepoPathBuf]>,
546 root: &CommitId,
547 head: &CommitId,
548 ) -> BackendResult<BoxStream<BackendResult<CopyRecord>>>;
549
550 fn gc(&self, index: &dyn Index, keep_newer: SystemTime) -> BackendResult<()>;
556}