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}