jj_core/backend.rs
1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Defines the commit backend trait and related types. This is the lowest-level
16//! trait for reading and writing commits, trees, files, etc.
17
18use std::any::Any;
19use std::borrow::Borrow;
20use std::fmt::Debug;
21use std::fmt::Write as _;
22use std::iter::zip;
23use std::pin::Pin;
24use std::slice;
25
26use async_trait::async_trait;
27use chrono::TimeZone as _;
28use futures::AsyncRead;
29use futures::stream::BoxStream;
30use smallvec::SmallVec;
31use thiserror::Error;
32
33use crate::conflict_labels::ConflictLabels;
34use crate::content_hash::ContentHash;
35use crate::hex_util;
36use crate::merge::Merge;
37use crate::object_id::ObjectId as _;
38use crate::object_id::id_type;
39use crate::repo_path::RepoPath;
40use crate::repo_path::RepoPathBuf;
41use crate::repo_path::RepoPathComponent;
42use crate::repo_path::RepoPathComponentBuf;
43use crate::signing::SignResult;
44
45id_type!(
46 /// Identifier for a [`Commit`] based on its content. When a commit is
47 /// rewritten, its `CommitId` changes.
48 pub CommitId { hex() }
49);
50id_type!(
51 /// Stable identifier for a [`Commit`]. Unlike the `CommitId`, the `ChangeId`
52 /// follows the commit and is not updated when the commit is rewritten.
53 pub ChangeId { reverse_hex() }
54);
55id_type!(
56 /// Identifier for a tree object.
57 pub TreeId { hex() }
58);
59id_type!(
60 /// Identifier for a file content.
61 pub FileId { hex() }
62);
63id_type!(
64 /// Identifier for a symlink.
65 pub SymlinkId { hex() }
66);
67id_type!(
68 /// Identifier for a copy history.
69 pub CopyId { hex() }
70);
71
72impl ChangeId {
73 /// Parses the given "reverse" hex string into a `ChangeId`.
74 pub fn try_from_reverse_hex(hex: impl AsRef<[u8]>) -> Option<Self> {
75 hex_util::decode_reverse_hex(hex).map(Self)
76 }
77
78 /// Returns the hex string representation of this ID, which uses `z-k`
79 /// "digits" instead of `0-9a-f`.
80 pub fn reverse_hex(&self) -> String {
81 hex_util::encode_reverse_hex(&self.0)
82 }
83}
84
85impl CopyId {
86 /// Returns a placeholder copy id to be used when we don't have a real copy
87 /// id yet.
88 // TODO: Delete this
89 pub fn placeholder() -> Self {
90 Self::new(vec![])
91 }
92}
93
94/// Error that may occur when converting a `Timestamp` to a `Datetime``.
95#[derive(Debug, Error)]
96#[error("Out-of-range date")]
97pub struct TimestampOutOfRange;
98
99/// The number of milliseconds since the Unix epoch.
100#[derive(ContentHash, Hash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
101pub struct MillisSinceEpoch(pub i64);
102
103/// A timestamp with millisecond precision and a time zone offset.
104#[derive(ContentHash, Hash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
105pub struct Timestamp {
106 /// The number of milliseconds since the Unix epoch.
107 pub timestamp: MillisSinceEpoch,
108 /// Timezone offset in minutes
109 pub tz_offset: i32,
110}
111
112impl Timestamp {
113 /// Returns the current local time as a `Timestamp`.
114 pub fn now() -> Self {
115 Self::from_datetime(chrono::offset::Local::now())
116 }
117
118 /// Creates a `Timestamp` from the given `DateTime`.
119 pub fn from_datetime<Tz: chrono::TimeZone<Offset = chrono::offset::FixedOffset>>(
120 datetime: chrono::DateTime<Tz>,
121 ) -> Self {
122 Self {
123 timestamp: MillisSinceEpoch(datetime.timestamp_millis()),
124 tz_offset: datetime.offset().local_minus_utc() / 60,
125 }
126 }
127
128 /// Converts this `Timestamp` to a `DateTime`.
129 pub fn to_datetime(
130 &self,
131 ) -> Result<chrono::DateTime<chrono::FixedOffset>, TimestampOutOfRange> {
132 let utc = match chrono::Utc.timestamp_opt(
133 self.timestamp.0.div_euclid(1000),
134 (self.timestamp.0.rem_euclid(1000)) as u32 * 1000000,
135 ) {
136 chrono::LocalResult::None => {
137 return Err(TimestampOutOfRange);
138 }
139 chrono::LocalResult::Single(x) => x,
140 chrono::LocalResult::Ambiguous(y, _z) => y,
141 };
142
143 Ok(utc.with_timezone(
144 &chrono::FixedOffset::east_opt(self.tz_offset * 60)
145 .unwrap_or_else(|| chrono::FixedOffset::east_opt(0).unwrap()),
146 ))
147 }
148}
149
150impl serde::Serialize for Timestamp {
151 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
152 where
153 S: serde::Serializer,
154 {
155 // TODO: test is_human_readable() to use raw format?
156 let t = self.to_datetime().map_err(serde::ser::Error::custom)?;
157 t.serialize(serializer)
158 }
159}
160
161/// Represents a person/entity and a timestamp for when they authored or
162/// committed a commit.
163#[derive(ContentHash, Hash, Debug, PartialEq, Eq, Clone, serde::Serialize)]
164pub struct Signature {
165 /// The name of the person/entity.
166 pub name: String,
167 /// The email address of the person/entity.
168 pub email: String,
169 /// The timestamp for when the person/entity authored or committed the
170 /// commit.
171 pub timestamp: Timestamp,
172}
173
174/// Represents a cryptographically signed [`Commit`] signature.
175#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]
176pub struct SecureSig {
177 /// The raw data that was signed to produce this signature.
178 pub data: Vec<u8>,
179 /// The signature itself.
180 pub sig: Vec<u8>,
181}
182
183/// Function called to sign a commit. The input is the raw data to sign, and the
184/// output is the signature.
185pub type SigningFn<'a> = dyn FnMut(&[u8]) -> SignResult<Vec<u8>> + Send + 'a;
186
187/// Represents a commit object, which contains a reference to the contents a
188/// that point in time, along with metadata about the commit.
189#[derive(ContentHash, Debug, PartialEq, Eq, Clone, serde::Serialize)]
190pub struct Commit {
191 /// The parent commits of this commit. Commits typically have one parents,
192 /// but they can have any number of parents. Only the root commit has no
193 /// parents.
194 pub parents: Vec<CommitId>,
195 /// The predecessor commits of this commit, i.e. commits that were rewritten
196 /// to create this commit.
197 //
198 // TODO: delete commit.predecessors when we can assume that most commits are
199 // tracked by op.commit_predecessors. (in jj 0.42 or so?)
200 #[serde(skip)] // deprecated
201 pub predecessors: Vec<CommitId>,
202 /// The tree at the root directory in this commit.
203 #[serde(skip)] // TODO: should be exposed?
204 pub root_tree: Merge<TreeId>,
205 /// If resolved, must be empty string. Otherwise, must have same number of
206 /// terms as `root_tree`.
207 #[serde(skip)]
208 pub conflict_labels: Merge<String>,
209 /// The change ID of this commit. This is a stable identifier that follows
210 /// the commit when it's rewritten.
211 pub change_id: ChangeId,
212 /// The description (commit message).
213 pub description: String,
214 /// The person/entity that authored this commit.
215 pub author: Signature,
216 /// The person/entity that committed this commit.
217 pub committer: Signature,
218 /// A cryptographic signature of this commit.
219 #[serde(skip)] // raw data wouldn't be useful
220 pub secure_sig: Option<SecureSig>,
221}
222
223/// An individual copy event, from file A -> B.
224#[derive(Debug, PartialEq, Eq, Clone)]
225pub struct CopyRecord {
226 /// The destination of the copy, B.
227 pub target: RepoPathBuf,
228 /// The CommitId where the copy took place.
229 pub target_commit: CommitId,
230 /// The source path a target was copied from.
231 ///
232 /// It is not required that the source path is different than the target
233 /// path. A custom backend may choose to represent 'rollbacks' as copies
234 /// from a file unto itself, from a specific prior commit.
235 pub source: RepoPathBuf,
236 /// The file id of the source file.
237 pub source_file: FileId,
238 /// The source commit the target was copied from. Backends may use this
239 /// field to implement 'integration' logic, where a source may be
240 /// periodically merged into a target, similar to a branch, but the
241 /// branching occurs at the file level rather than the repository level. It
242 /// also follows naturally that any copy source targeted to a specific
243 /// commit should avoid copy propagation on rebasing, which is desirable
244 /// for 'fork' style copies.
245 ///
246 /// It is required that the commit id is an ancestor of the commit with
247 /// which this copy source is associated.
248 pub source_commit: CommitId,
249}
250
251/// Describes the copy history of a file. The copy object is unchanged when a
252/// file is modified.
253#[derive(ContentHash, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
254pub struct CopyHistory {
255 /// The file's current path.
256 pub current_path: RepoPathBuf,
257 /// IDs of the files that became the current incarnation of this file.
258 ///
259 /// A newly created file has no parents. A regular copy or rename has one
260 /// parent. A merge of multiple files has multiple parents.
261 pub parents: Vec<CopyId>,
262 /// An optional piece of data to give the Copy object a different ID. May be
263 /// randomly generated. This allows a commit to say that a file was replaced
264 /// by a new incarnation of it, indicating a logically distinct file
265 /// taking the place of the previous file at the path.
266 pub salt: Vec<u8>,
267}
268
269/// A `CopyHistory` along with its `CopyId`.
270#[derive(Debug, Eq, PartialEq)]
271pub struct RelatedCopy {
272 /// The copy id.
273 pub id: CopyId,
274 /// The copy history.
275 pub history: CopyHistory,
276}
277
278/// Error that may occur during backend initialization.
279#[derive(Debug, Error)]
280#[error(transparent)]
281pub struct BackendInitError(pub Box<dyn std::error::Error + Send + Sync>);
282
283/// Error that may occur during backend loading.
284#[derive(Debug, Error)]
285#[error(transparent)]
286pub struct BackendLoadError(pub Box<dyn std::error::Error + Send + Sync>);
287
288/// Commit-backend error that may occur after the backend is loaded.
289#[derive(Debug, Error)]
290pub enum BackendError {
291 /// The caller attempted to read an object by specifying an ID with an
292 /// invalid hash length for this backend.
293 #[error(
294 "Invalid hash length for object of type {object_type} (expected {expected} bytes, got \
295 {actual} bytes): {hash}"
296 )]
297 InvalidHashLength {
298 /// The expected length of the hash in bytes for this backend.
299 expected: usize,
300 /// The actual length of the hash in bytes that was provided.
301 actual: usize,
302 /// The type of the object that we attempted to read, e.g. "commit" or
303 /// "tree".
304 object_type: String,
305 /// The hex hash that had an invalid length.
306 hash: String,
307 },
308 /// The caller attempted to read an object that internally stored as invalid
309 /// UTF-8, such as a symlink target with invalid UTF-8 stored in the Git
310 /// backend.
311 #[error("Invalid UTF-8 for object {hash} of type {object_type}")]
312 InvalidUtf8 {
313 /// The type of the object that we attempted to read, e.g. "commit" or
314 /// "tree".
315 object_type: String,
316 /// The hex hash of the object that had invalid UTF-8.
317 hash: String,
318 /// The source error.
319 source: std::str::Utf8Error,
320 },
321 /// The caller attempted to read an object that doesn't exist.
322 #[error("Object {hash} of type {object_type} not found")]
323 ObjectNotFound {
324 /// The type of the object that we attempted to read, e.g. "commit" or
325 /// "tree".
326 object_type: String,
327 /// The hex hash of the object that was not found.
328 hash: String,
329 /// The source error.
330 source: Option<Box<dyn std::error::Error + Send + Sync>>,
331 },
332 /// Failed to read an object due to an I/O error or other unexpected error.
333 #[error("Error when reading object {hash} of type {object_type}")]
334 ReadObject {
335 /// The type of the object that we attempted to read, e.g. "commit" or
336 /// "tree".
337 object_type: String,
338 /// The hex hash of the object that we failed to read.
339 hash: String,
340 /// The source error.
341 source: Box<dyn std::error::Error + Send + Sync>,
342 },
343 /// The caller attempted to read an object but doesn't have permission to
344 /// read it.
345 #[error("Access denied to read object {hash} of type {object_type}")]
346 ReadAccessDenied {
347 /// The type of the object that we attempted to read, e.g. "commit" or
348 /// "tree".
349 object_type: String,
350 /// The hex hash of the object that the caller doesn't have permission
351 /// to read.
352 hash: String,
353 /// The source error.
354 source: Box<dyn std::error::Error + Send + Sync>,
355 },
356 /// Failed to read a file's content due to an I/O error or other unexpected
357 /// error.
358 #[error(
359 "Error when reading file content for file {path} with id {id}",
360 path = path.as_internal_file_string()
361 )]
362 ReadFile {
363 /// The path of the file we failed to read.
364 path: RepoPathBuf,
365 /// The ID of the file we failed to read.
366 id: FileId,
367 /// The source error.
368 source: Box<dyn std::error::Error + Send + Sync>,
369 },
370 /// Failed to write an object due to an I/O error or other unexpected error.
371 #[error("Could not write object of type {object_type}")]
372 WriteObject {
373 /// The type of the object that we attempted to write, e.g. "commit" or
374 /// "tree".
375 object_type: &'static str,
376 /// The source error.
377 source: Box<dyn std::error::Error + Send + Sync>,
378 },
379 /// Some other error that doesn't fit into the above categories.
380 #[error(transparent)]
381 Other(Box<dyn std::error::Error + Send + Sync>),
382 /// A valid operation was attempted, but it failed because it isn't
383 /// supported by the particular backend.
384 #[error("{0}")]
385 Unsupported(String),
386}
387
388/// A specialized [`Result`] type for commit backend errors.
389pub type BackendResult<T> = Result<T, BackendError>;
390
391/// Identifies the content at a given path in a tree.
392#[derive(ContentHash, Debug, PartialEq, Eq, Clone, Hash)]
393pub enum TreeValue {
394 // TODO: When there's a CopyId here, the copy object's path must match
395 // the path identified by the tree.
396 /// This path is a regular file, possibly executable.
397 File {
398 /// The file's content ID.
399 id: FileId,
400 /// Whether the file is executable.
401 executable: bool,
402 /// The copy id.
403 copy_id: CopyId,
404 },
405 /// This path is a symbolic link.
406 Symlink(SymlinkId),
407 /// This path is a directory.
408 Tree(TreeId),
409 /// This path is a Git submodule.
410 GitSubmodule(CommitId),
411}
412
413impl TreeValue {
414 /// The copy id if this value represents a file.
415 pub fn copy_id(&self) -> Option<&CopyId> {
416 match self {
417 Self::File { copy_id, .. } => Some(copy_id),
418 _ => None,
419 }
420 }
421}
422
423/// Borrowed `MergedTreeValue`.
424pub type MergedTreeVal<'a> = Merge<Option<&'a TreeValue>>;
425
426/// The value at a given path in a commit.
427///
428/// It depends on the context whether it can be absent
429/// (`Merge::is_absent()`). For example, when getting the value at a
430/// specific path, it may be, but when iterating over entries in a
431/// tree, it shouldn't be.
432pub type MergedTreeValue = Merge<Option<TreeValue>>;
433
434/// Extension methods for tree-value merges such as [`MergedTreeValue`] and
435/// [`MergedTreeVal`].
436pub trait MergedTreeValueExt {
437 /// Whether this merge should be recursed into when doing directory walks.
438 fn is_tree(&self) -> bool;
439
440 /// Whether this merge is present and not a tree
441 fn is_file_like(&self) -> bool;
442
443 /// If this merge contains only files or absent entries, returns a merge of
444 /// the `FileId`s. The executable bits and copy IDs will be ignored. Use
445 /// `Merge::with_new_file_ids()` to produce a new merge with the original
446 /// executable bits preserved.
447 fn to_file_merge(&self) -> Option<Merge<Option<FileId>>>;
448
449 /// If this merge contains only files or absent entries, returns a merge of
450 /// the files' executable bits.
451 fn to_executable_merge(&self) -> Option<Merge<Option<bool>>>;
452
453 /// If this merge contains only files or absent entries, returns a merge of
454 /// the files' copy IDs.
455 fn to_copy_id_merge(&self) -> Option<Merge<Option<CopyId>>>;
456
457 /// Creates a new merge with the file ids from the given merge. In other
458 /// words, the executable bits and copy IDs from `self` will be preserved.
459 ///
460 /// The given `file_ids` should have the same shape as `self`. Only the
461 /// `FileId` values may differ.
462 fn with_new_file_ids(&self, file_ids: &Merge<Option<FileId>>) -> Merge<Option<TreeValue>>;
463
464 /// Give a summary description of the conflict's "removes" and "adds"
465 fn describe(&self, labels: &ConflictLabels) -> String;
466}
467
468impl<T> MergedTreeValueExt for Merge<Option<T>>
469where
470 T: Borrow<TreeValue>,
471{
472 fn is_tree(&self) -> bool {
473 self.is_present()
474 && self.iter().all(|value| {
475 matches!(
476 borrow_tree_value(value.as_ref()),
477 Some(TreeValue::Tree(_)) | None
478 )
479 })
480 }
481
482 fn is_file_like(&self) -> bool {
483 self.is_present() && !self.is_tree()
484 }
485
486 fn to_file_merge(&self) -> Option<Merge<Option<FileId>>> {
487 let file_ids = self
488 .try_map(|term| match borrow_tree_value(term.as_ref()) {
489 None => Ok(None),
490 Some(TreeValue::File {
491 id,
492 executable: _,
493 copy_id: _,
494 }) => Ok(Some(id.clone())),
495 _ => Err(()),
496 })
497 .ok()?;
498
499 Some(file_ids)
500 }
501
502 fn to_executable_merge(&self) -> Option<Merge<Option<bool>>> {
503 self.try_map(|term| match borrow_tree_value(term.as_ref()) {
504 None => Ok(None),
505 Some(TreeValue::File {
506 id: _,
507 executable,
508 copy_id: _,
509 }) => Ok(Some(*executable)),
510 _ => Err(()),
511 })
512 .ok()
513 }
514
515 fn to_copy_id_merge(&self) -> Option<Merge<Option<CopyId>>> {
516 self.try_map(|term| match borrow_tree_value(term.as_ref()) {
517 None => Ok(None),
518 Some(TreeValue::File {
519 id: _,
520 executable: _,
521 copy_id,
522 }) => Ok(Some(copy_id.clone())),
523 _ => Err(()),
524 })
525 .ok()
526 }
527
528 fn with_new_file_ids(&self, file_ids: &Merge<Option<FileId>>) -> Merge<Option<TreeValue>> {
529 assert_eq!(self.num_sides(), file_ids.num_sides());
530 let values: SmallVec<_> = zip(self, file_ids.iter().cloned())
531 .map(
532 |(tree_value, file_id)| match (borrow_tree_value(tree_value.as_ref()), file_id) {
533 (
534 Some(TreeValue::File {
535 id: _,
536 executable,
537 copy_id,
538 }),
539 Some(id),
540 ) => Some(TreeValue::File {
541 id,
542 executable: *executable,
543 copy_id: copy_id.clone(),
544 }),
545 (None, None) => None,
546 // New files are populated to preserve the materialized conflict. The file won't
547 // be checked out to the disk. So the metadata is not important, and we will
548 // just use the default values.
549 (None, Some(id)) => Some(TreeValue::File {
550 id,
551 executable: false,
552 copy_id: CopyId::placeholder(),
553 }),
554 (old, new) => panic!("incompatible update: {old:?} to {new:?}"),
555 },
556 )
557 .collect();
558 Merge::from_vec(values)
559 }
560
561 fn describe(&self, labels: &ConflictLabels) -> String {
562 let mut buf = String::new();
563 writeln!(buf, "Conflict:").unwrap();
564 for (term, label) in self
565 .removes()
566 .enumerate()
567 .filter_map(|(i, term)| term.as_ref().map(|term| (term, labels.get_remove(i))))
568 {
569 write!(buf, " Removing {}", describe_conflict_term(term.borrow())).unwrap();
570 if let Some(label) = label {
571 write!(buf, " ({label})").unwrap();
572 }
573 buf.push('\n');
574 }
575 for (term, label) in self
576 .adds()
577 .enumerate()
578 .filter_map(|(i, term)| term.as_ref().map(|term| (term, labels.get_add(i))))
579 {
580 write!(buf, " Adding {}", describe_conflict_term(term.borrow())).unwrap();
581 if let Some(label) = label {
582 write!(buf, " ({label})").unwrap();
583 }
584 buf.push('\n');
585 }
586 buf
587 }
588}
589
590/// Borrow the Option's `TreeValue` if it exists.
591pub fn borrow_tree_value<T: Borrow<TreeValue> + ?Sized>(term: Option<&T>) -> Option<&TreeValue> {
592 term.map(|value| value.borrow())
593}
594
595fn describe_conflict_term(value: &TreeValue) -> String {
596 match value {
597 TreeValue::File {
598 id,
599 executable: false,
600 copy_id: _,
601 } => {
602 // TODO: include the copy here once we start using it
603 format!("file with id {id}")
604 }
605 TreeValue::File {
606 id,
607 executable: true,
608 copy_id: _,
609 } => {
610 // TODO: include the copy here once we start using it
611 format!("executable file with id {id}")
612 }
613 TreeValue::Symlink(id) => {
614 format!("symlink with id {id}")
615 }
616 TreeValue::Tree(id) => {
617 format!("tree with id {id}")
618 }
619 TreeValue::GitSubmodule(id) => {
620 format!("Git submodule with id {id}")
621 }
622 }
623}
624
625/// An entry in a `Tree` consisting of a basename and a `TreeValue`.
626#[derive(Debug, PartialEq, Eq, Clone)]
627pub struct TreeEntry<'a> {
628 name: &'a RepoPathComponent,
629 value: &'a TreeValue,
630}
631
632impl<'a> TreeEntry<'a> {
633 /// Creates a new `TreeEntry` with the given name and value.
634 pub fn new(name: &'a RepoPathComponent, value: &'a TreeValue) -> Self {
635 Self { name, value }
636 }
637
638 /// Returns the basename at this path.
639 pub fn name(&self) -> &'a RepoPathComponent {
640 self.name
641 }
642
643 /// Returns the tree value at this path.
644 pub fn value(&self) -> &'a TreeValue {
645 self.value
646 }
647}
648
649/// Iterator over the direct entries in a `Tree`.
650pub struct TreeEntriesNonRecursiveIterator<'a> {
651 iter: slice::Iter<'a, (RepoPathComponentBuf, TreeValue)>,
652}
653
654impl<'a> Iterator for TreeEntriesNonRecursiveIterator<'a> {
655 type Item = TreeEntry<'a>;
656
657 fn next(&mut self) -> Option<Self::Item> {
658 self.iter
659 .next()
660 .map(|(name, value)| TreeEntry { name, value })
661 }
662}
663
664/// A tree object, which represents a directory. It contains the direct entries
665/// of the directory. Subdirectories are represented by the `TreeValue::Tree`
666/// variant. The `Tree` object associated with the root directory thus
667/// represents the entire repository at a given point in time.
668///
669/// The entries must be sorted (by `RepoPathComponentBuf`'s ordering) and must
670/// not contain duplicate names.
671#[derive(ContentHash, Default, PartialEq, Eq, Debug, Clone)]
672pub struct Tree {
673 entries: Vec<(RepoPathComponentBuf, TreeValue)>,
674}
675
676impl Tree {
677 /// Creates a new `Tree` from the given entries. The entries must be sorted
678 /// by name and must not contain duplicate names.
679 pub fn from_sorted_entries(entries: Vec<(RepoPathComponentBuf, TreeValue)>) -> Self {
680 debug_assert!(entries.is_sorted_by(|(a, _), (b, _)| a < b));
681 Self { entries }
682 }
683
684 /// Checks if this tree has no entries.
685 pub fn is_empty(&self) -> bool {
686 self.entries.is_empty()
687 }
688
689 /// Returns an iterator over the names of the entries in this tree.
690 pub fn names(&self) -> impl Iterator<Item = &RepoPathComponent> {
691 self.entries.iter().map(|(name, _)| name.as_ref())
692 }
693
694 /// Returns an iterator over the entries in this tree.
695 pub fn entries(&self) -> TreeEntriesNonRecursiveIterator<'_> {
696 TreeEntriesNonRecursiveIterator {
697 iter: self.entries.iter(),
698 }
699 }
700
701 /// Returns the entry at the given basename, if it exists.
702 pub fn entry(&self, name: &RepoPathComponent) -> Option<TreeEntry<'_>> {
703 let index = self
704 .entries
705 .binary_search_by_key(&name, |(name, _)| name)
706 .ok()?;
707 let (name, value) = &self.entries[index];
708 Some(TreeEntry { name, value })
709 }
710
711 /// Returns the value at the given basename, if it exists.
712 pub fn value(&self, name: &RepoPathComponent) -> Option<&TreeValue> {
713 self.entry(name).map(|entry| entry.value)
714 }
715}
716
717/// Creates a root commit object.
718pub fn make_root_commit(root_change_id: ChangeId, empty_tree_id: TreeId) -> Commit {
719 let timestamp = Timestamp {
720 timestamp: MillisSinceEpoch(0),
721 tz_offset: 0,
722 };
723 let signature = Signature {
724 name: String::new(),
725 email: String::new(),
726 timestamp,
727 };
728 Commit {
729 parents: vec![],
730 predecessors: vec![],
731 root_tree: Merge::resolved(empty_tree_id),
732 conflict_labels: Merge::resolved(String::new()),
733 change_id: root_change_id,
734 description: String::new(),
735 author: signature.clone(),
736 committer: signature,
737 secure_sig: None,
738 }
739}
740
741/// Defines the interface for commit backends.
742#[async_trait]
743pub trait Backend: Any + Send + Sync + Debug {
744 /// A unique name that identifies this backend. Written to
745 /// `.jj/repo/store/type` when the repo is created.
746 fn name(&self) -> &str;
747
748 /// The length of commit IDs in bytes.
749 fn commit_id_length(&self) -> usize;
750
751 /// The length of change IDs in bytes.
752 fn change_id_length(&self) -> usize;
753
754 /// The root commit's ID.
755 ///
756 /// The root commit is a possibly virtual commit that is an ancestor of all
757 /// commits in the repository. It is the only commit that has no parents.
758 fn root_commit_id(&self) -> &CommitId;
759
760 /// The root commit's change ID.
761 fn root_change_id(&self) -> &ChangeId;
762
763 /// The empty tree's ID. All empty trees must have the same ID regardless of
764 /// the path.
765 fn empty_tree_id(&self) -> &TreeId;
766
767 /// An estimate of how many concurrent requests this backend handles well. A
768 /// local backend like the Git backend (at until it supports partial clones)
769 /// may want to set this to 1. A cloud-backed backend may want to set it to
770 /// 100 or so.
771 /// It is guaranteed to return at least 1.
772 ///
773 /// It is not guaranteed that at most this number of concurrent requests are
774 /// sent. It is the backend's responsibility to make sure it doesn't put
775 /// too much load on its storage, e.g. by queueing requests if necessary.
776 fn concurrency(&self) -> usize;
777
778 /// Returns a reader for reading the contents of a file from the backend.
779 async fn read_file(
780 &self,
781 path: &RepoPath,
782 id: &FileId,
783 ) -> BackendResult<Pin<Box<dyn AsyncRead + Send>>>;
784
785 /// Writes the contents of the writer to the backend. Returns the ID of the
786 /// written file.
787 async fn write_file(
788 &self,
789 path: &RepoPath,
790 contents: &mut (dyn AsyncRead + Send + Unpin),
791 ) -> BackendResult<FileId>;
792
793 /// Reads the target of a symlink from the backend. Returns the target path.
794 /// It is not a `RepoPath` because it doesn't necessarily point within the
795 /// repository.
796 async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String>;
797
798 /// Writes a symlink with the given target to the backend and returns its
799 /// ID.
800 async fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult<SymlinkId>;
801
802 /// Read the specified `CopyHistory` object.
803 ///
804 /// Backends that don't support copy tracking may return
805 /// `BackendError::Unsupported`.
806 async fn read_copy(&self, id: &CopyId) -> BackendResult<CopyHistory>;
807
808 /// Write the `CopyHistory` object and return its ID.
809 ///
810 /// Backends that don't support copy tracking may return
811 /// `BackendError::Unsupported`.
812 async fn write_copy(&self, copy: &CopyHistory) -> BackendResult<CopyId>;
813
814 /// Find all copy histories that are related to the specified one. This is
815 /// defined as those that are ancestors of the given specified one, plus
816 /// all descendants of those ancestors. Children must be returned before
817 /// parents, and the order should be deterministic.
818 ///
819 /// It is valid (but wasteful) to include other copy histories, such as
820 /// siblings, or even completely unrelated copy histories.
821 ///
822 /// Backends that don't support copy tracking may return
823 /// `BackendError::Unsupported`.
824 async fn get_related_copies(&self, copy_id: &CopyId) -> BackendResult<Vec<RelatedCopy>>;
825
826 /// Reads the tree at the given path with the given ID.
827 async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree>;
828
829 /// Writes the given tree at the given path to the backend and returns its
830 /// ID.
831 async fn write_tree(&self, path: &RepoPath, contents: &Tree) -> BackendResult<TreeId>;
832
833 /// Reads the commit with the given ID.
834 async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit>;
835
836 /// Writes a commit and returns its ID and the commit itself. The commit
837 /// should contain the data that was actually written, which may differ
838 /// from the data passed in. For example, the backend may change the
839 /// committer name to an authenticated user's name, or the backend's
840 /// timestamps may have less precision than the millisecond precision in
841 /// `Commit`.
842 ///
843 /// The `sign_with` parameter could contain a function to cryptographically
844 /// sign some binary representation of the commit.
845 /// If the backend supports it, it could call it and store the result in
846 /// an implementation specific fashion, and both `read_commit` and the
847 /// return of `write_commit` should read it back as the `secure_sig`
848 /// field.
849 async fn write_commit(
850 &self,
851 contents: Commit,
852 sign_with: Option<&mut SigningFn>,
853 ) -> BackendResult<(CommitId, Commit)>;
854
855 /// Get copy records for the dag range `root..head`. If `paths` is None
856 /// include all paths, otherwise restrict to only `paths`.
857 ///
858 /// The exact order these are returned is unspecified, but it is guaranteed
859 /// to be reverse-topological. That is, for any two copy records with
860 /// different commit ids A and B, if A is an ancestor of B, A is streamed
861 /// after B.
862 ///
863 /// Streaming by design to better support large backends which may have very
864 /// large single-file histories. This also allows more iterative algorithms
865 /// like blame/annotate to short-circuit after a point without wasting
866 /// unnecessary resources.
867 fn get_copy_records(
868 &self,
869 paths: Option<&[RepoPathBuf]>,
870 root: &CommitId,
871 head: &CommitId,
872 ) -> BackendResult<BoxStream<'_, BackendResult<CopyRecord>>>;
873}
874
875impl dyn Backend {
876 /// Returns reference of the implementation type.
877 pub fn downcast_ref<T: Backend>(&self) -> Option<&T> {
878 (self as &dyn Any).downcast_ref()
879 }
880}
881
882#[cfg(test)]
883mod tests {
884 use super::*;
885
886 #[test]
887 fn test_display_object_id() {
888 let commit_id = CommitId::from_hex("deadbeef0123");
889 assert_eq!(format!("{commit_id}"), "deadbeef0123");
890 assert_eq!(format!("{commit_id:.6}"), "deadbe");
891
892 let change_id = ChangeId::from_hex("deadbeef0123");
893 assert_eq!(format!("{change_id}"), "mlpmollkzyxw");
894 assert_eq!(format!("{change_id:.6}"), "mlpmol");
895 }
896}