Skip to main content

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}