Skip to main content

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