563 lines · 18.4 KB · Rust
RawDownload
// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. #![expect(missing_docs)] use std::any::Any;use std::fmt::Debug;use std::pin::Pin;use std::slice;use std::time::SystemTime; use async_trait::async_trait;use chrono::TimeZone as _;use futures::stream::BoxStream;use thiserror::Error;use tokio::io::AsyncRead; use crate::content_hash::ContentHash;use crate::hex_util;use crate::index::Index;use crate::merge::Merge;use crate::object_id::ObjectId as _;use crate::object_id::id_type;use crate::repo_path::RepoPath;use crate::repo_path::RepoPathBuf;use crate::repo_path::RepoPathComponent;use crate::repo_path::RepoPathComponentBuf;use crate::signing::SignResult; id_type!(    /// Identifier for a [`Commit`] based on its content. When a commit is    /// rewritten, its `CommitId` changes.    pub CommitId { hex() });id_type!(    /// Stable identifier for a [`Commit`]. Unlike the `CommitId`, the `ChangeId`    /// follows the commit and is not updated when the commit is rewritten.    pub ChangeId { reverse_hex() });id_type!(pub TreeId { hex() });id_type!(pub FileId { hex() });id_type!(pub SymlinkId { hex() });id_type!(pub CopyId { hex() }); impl ChangeId {    /// Parses the given "reverse" hex string into a `ChangeId`.    pub fn try_from_reverse_hex(hex: impl AsRef<[u8]>) -> Option<Self> {        hex_util::decode_reverse_hex(hex).map(Self)    }     /// Returns the hex string representation of this ID, which uses `z-k`    /// "digits" instead of `0-9a-f`.    pub fn reverse_hex(&self) -> String {        hex_util::encode_reverse_hex(&self.0)    }} impl CopyId {    /// Returns a placeholder copy id to be used when we don't have a real copy    /// id yet.    // TODO: Delete this    pub fn placeholder() -> Self {        Self::new(vec![])    }} #[derive(Debug, Error)]#[error("Out-of-range date")]pub struct TimestampOutOfRange; #[derive(ContentHash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]pub struct MillisSinceEpoch(pub i64); #[derive(ContentHash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]pub struct Timestamp {    pub timestamp: MillisSinceEpoch,    // time zone offset in minutes    pub tz_offset: i32,} impl Timestamp {    pub fn now() -> Self {        Self::from_datetime(chrono::offset::Local::now())    }     pub fn from_datetime<Tz: chrono::TimeZone<Offset = chrono::offset::FixedOffset>>(        datetime: chrono::DateTime<Tz>,    ) -> Self {        Self {            timestamp: MillisSinceEpoch(datetime.timestamp_millis()),            tz_offset: datetime.offset().local_minus_utc() / 60,        }    }     pub fn to_datetime(        &self,    ) -> Result<chrono::DateTime<chrono::FixedOffset>, TimestampOutOfRange> {        let utc = match chrono::Utc.timestamp_opt(            self.timestamp.0.div_euclid(1000),            (self.timestamp.0.rem_euclid(1000)) as u32 * 1000000,        ) {            chrono::LocalResult::None => {                return Err(TimestampOutOfRange);            }            chrono::LocalResult::Single(x) => x,            chrono::LocalResult::Ambiguous(y, _z) => y,        };         Ok(utc.with_timezone(            &chrono::FixedOffset::east_opt(self.tz_offset * 60)                .unwrap_or_else(|| chrono::FixedOffset::east_opt(0).unwrap()),        ))    }} impl serde::Serialize for Timestamp {    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>    where        S: serde::Serializer,    {        // TODO: test is_human_readable() to use raw format?        let t = self.to_datetime().map_err(serde::ser::Error::custom)?;        t.serialize(serializer)    }} /// Represents a [`Commit`] signature.#[derive(ContentHash, Debug, PartialEq, Eq, Clone, serde::Serialize)]pub struct Signature {    pub name: String,    pub email: String,    pub timestamp: Timestamp,} /// Represents a cryptographically signed [`Commit`] signature.#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]pub struct SecureSig {    pub data: Vec<u8>,    pub sig: Vec<u8>,} pub type SigningFn<'a> = dyn FnMut(&[u8]) -> SignResult<Vec<u8>> + Send + 'a; /// Identifies a merge of multiple trees. Can be read as a `MergedTree`.// TODO: this type doesn't add anything over `Merge<TreeId>` currently, but conflict labels could be// added here in the future if we also add them to `MergedTree`.#[derive(ContentHash, Debug, PartialEq, Eq, Clone)]pub struct MergedTreeId {    /// The tree id(s) of a merge tree    tree_ids: Merge<TreeId>,} impl MergedTreeId {    /// Create a resolved `MergedTreeId` from a single regular tree.    pub fn resolved(tree_id: TreeId) -> Self {        Self::new(Merge::resolved(tree_id))    }     /// Create a `MergedTreeId` from a `Merge<TreeId>`.    pub fn new(tree_ids: Merge<TreeId>) -> Self {        Self { tree_ids }    }     /// Returns the underlying `Merge<TreeId>`.    pub fn as_merge(&self) -> &Merge<TreeId> {        &self.tree_ids    }     /// Extracts the underlying `Merge<TreeId>`.    pub fn into_merge(self) -> Merge<TreeId> {        self.tree_ids    }} #[derive(ContentHash, Debug, PartialEq, Eq, Clone, serde::Serialize)]pub struct Commit {    pub parents: Vec<CommitId>,    // TODO: delete commit.predecessors when we can assume that most commits are    // tracked by op.commit_predecessors. (in jj 0.42 or so?)    #[serde(skip)] // deprecated    pub predecessors: Vec<CommitId>,    #[serde(skip)] // TODO: should be exposed?    pub root_tree: MergedTreeId,    pub change_id: ChangeId,    pub description: String,    pub author: Signature,    pub committer: Signature,    #[serde(skip)] // raw data wouldn't be useful    pub secure_sig: Option<SecureSig>,} /// An individual copy event, from file A -> B.#[derive(Debug, PartialEq, Eq, Clone)]pub struct CopyRecord {    /// The destination of the copy, B.    pub target: RepoPathBuf,    /// The CommitId where the copy took place.    pub target_commit: CommitId,    /// The source path a target was copied from.    ///    /// It is not required that the source path is different than the target    /// path. A custom backend may choose to represent 'rollbacks' as copies    /// from a file unto itself, from a specific prior commit.    pub source: RepoPathBuf,    pub source_file: FileId,    /// The source commit the target was copied from. Backends may use this    /// field to implement 'integration' logic, where a source may be    /// periodically merged into a target, similar to a branch, but the    /// branching occurs at the file level rather than the repository level. It    /// also follows naturally that any copy source targeted to a specific    /// commit should avoid copy propagation on rebasing, which is desirable    /// for 'fork' style copies.    ///    /// It is required that the commit id is an ancestor of the commit with    /// which this copy source is associated.    pub source_commit: CommitId,} /// Describes the copy history of a file. The copy object is unchanged when a/// file is modified.#[derive(ContentHash, Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]pub struct CopyHistory {    /// The file's current path.    pub current_path: RepoPathBuf,    /// IDs of the files that became the current incarnation of this file.    ///    /// A newly created file has no parents. A regular copy or rename has one    /// parent. A merge of multiple files has multiple parents.    pub parents: Vec<CopyId>,    /// An optional piece of data to give the Copy object a different ID. May be    /// randomly generated. This allows a commit to say that a file was replaced    /// by a new incarnation of it, indicating a logically distinct file    /// taking the place of the previous file at the path.    pub salt: Vec<u8>,} /// Error that may occur during backend initialization.#[derive(Debug, Error)]#[error(transparent)]pub struct BackendInitError(pub Box<dyn std::error::Error + Send + Sync>); /// Error that may occur during backend loading.#[derive(Debug, Error)]#[error(transparent)]pub struct BackendLoadError(pub Box<dyn std::error::Error + Send + Sync>); /// Commit-backend error that may occur after the backend is loaded.#[derive(Debug, Error)]pub enum BackendError {    #[error(        "Invalid hash length for object of type {object_type} (expected {expected} bytes, got \         {actual} bytes): {hash}"    )]    InvalidHashLength {        expected: usize,        actual: usize,        object_type: String,        hash: String,    },    #[error("Invalid UTF-8 for object {hash} of type {object_type}")]    InvalidUtf8 {        object_type: String,        hash: String,        source: std::str::Utf8Error,    },    #[error("Object {hash} of type {object_type} not found")]    ObjectNotFound {        object_type: String,        hash: String,        source: Box<dyn std::error::Error + Send + Sync>,    },    #[error("Error when reading object {hash} of type {object_type}")]    ReadObject {        object_type: String,        hash: String,        source: Box<dyn std::error::Error + Send + Sync>,    },    #[error("Access denied to read object {hash} of type {object_type}")]    ReadAccessDenied {        object_type: String,        hash: String,        source: Box<dyn std::error::Error + Send + Sync>,    },    #[error(        "Error when reading file content for file {path} with id {id}",        path = path.as_internal_file_string()    )]    ReadFile {        path: RepoPathBuf,        id: FileId,        source: Box<dyn std::error::Error + Send + Sync>,    },    #[error("Could not write object of type {object_type}")]    WriteObject {        object_type: &'static str,        source: Box<dyn std::error::Error + Send + Sync>,    },    #[error(transparent)]    Other(Box<dyn std::error::Error + Send + Sync>),    /// A valid operation attempted, but failed because it isn't supported by    /// the particular backend.    #[error("{0}")]    Unsupported(String),} pub type BackendResult<T> = Result<T, BackendError>; #[derive(ContentHash, Debug, PartialEq, Eq, Clone, Hash)]pub enum TreeValue {    // TODO: When there's a CopyId here, the copy object's path must match    // the path identified by the tree.    File {        id: FileId,        executable: bool,        copy_id: CopyId,    },    Symlink(SymlinkId),    Tree(TreeId),    GitSubmodule(CommitId),} impl TreeValue {    pub fn hex(&self) -> String {        match self {            Self::File { id, .. } => id.hex(),            Self::Symlink(id) => id.hex(),            Self::Tree(id) => id.hex(),            Self::GitSubmodule(id) => id.hex(),        }    }} #[derive(Debug, PartialEq, Eq, Clone)]pub struct TreeEntry<'a> {    name: &'a RepoPathComponent,    value: &'a TreeValue,} impl<'a> TreeEntry<'a> {    pub fn new(name: &'a RepoPathComponent, value: &'a TreeValue) -> Self {        Self { name, value }    }     pub fn name(&self) -> &'a RepoPathComponent {        self.name    }     pub fn value(&self) -> &'a TreeValue {        self.value    }} pub struct TreeEntriesNonRecursiveIterator<'a> {    iter: slice::Iter<'a, (RepoPathComponentBuf, TreeValue)>,} impl<'a> Iterator for TreeEntriesNonRecursiveIterator<'a> {    type Item = TreeEntry<'a>;     fn next(&mut self) -> Option<Self::Item> {        self.iter            .next()            .map(|(name, value)| TreeEntry { name, value })    }} #[derive(ContentHash, Default, PartialEq, Eq, Debug, Clone)]pub struct Tree {    entries: Vec<(RepoPathComponentBuf, TreeValue)>,} impl Tree {    pub fn from_sorted_entries(entries: Vec<(RepoPathComponentBuf, TreeValue)>) -> Self {        debug_assert!(entries.is_sorted_by(|(a, _), (b, _)| a < b));        Self { entries }    }     pub fn is_empty(&self) -> bool {        self.entries.is_empty()    }     pub fn names(&self) -> impl Iterator<Item = &RepoPathComponent> {        self.entries.iter().map(|(name, _)| name.as_ref())    }     pub fn entries(&self) -> TreeEntriesNonRecursiveIterator<'_> {        TreeEntriesNonRecursiveIterator {            iter: self.entries.iter(),        }    }     pub fn entry(&self, name: &RepoPathComponent) -> Option<TreeEntry<'_>> {        let index = self            .entries            .binary_search_by_key(&name, |(name, _)| name)            .ok()?;        let (name, value) = &self.entries[index];        Some(TreeEntry { name, value })    }     pub fn value(&self, name: &RepoPathComponent) -> Option<&TreeValue> {        self.entry(name).map(|entry| entry.value)    }} pub fn make_root_commit(root_change_id: ChangeId, empty_tree_id: TreeId) -> Commit {    let timestamp = Timestamp {        timestamp: MillisSinceEpoch(0),        tz_offset: 0,    };    let signature = Signature {        name: String::new(),        email: String::new(),        timestamp,    };    Commit {        parents: vec![],        predecessors: vec![],        root_tree: MergedTreeId::resolved(empty_tree_id),        change_id: root_change_id,        description: String::new(),        author: signature.clone(),        committer: signature,        secure_sig: None,    }} /// Defines the interface for commit backends.#[async_trait]pub trait Backend: Any + Send + Sync + Debug {    /// A unique name that identifies this backend. Written to    /// `.jj/repo/store/type` when the repo is created.    fn name(&self) -> &str;     /// The length of commit IDs in bytes.    fn commit_id_length(&self) -> usize;     /// The length of change IDs in bytes.    fn change_id_length(&self) -> usize;     fn root_commit_id(&self) -> &CommitId;     fn root_change_id(&self) -> &ChangeId;     fn empty_tree_id(&self) -> &TreeId;     /// An estimate of how many concurrent requests this backend handles well. A    /// local backend like the Git backend (at until it supports partial clones)    /// may want to set this to 1. A cloud-backed backend may want to set it to    /// 100 or so.    ///    /// It is not guaranteed that at most this number of concurrent requests are    /// sent.    fn concurrency(&self) -> usize;     async fn read_file(        &self,        path: &RepoPath,        id: &FileId,    ) -> BackendResult<Pin<Box<dyn AsyncRead + Send>>>;     async fn write_file(        &self,        path: &RepoPath,        contents: &mut (dyn AsyncRead + Send + Unpin),    ) -> BackendResult<FileId>;     async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String>;     async fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult<SymlinkId>;     /// Read the specified `CopyHistory` object.    ///    /// Backends that don't support copy tracking may return    /// `BackendError::Unsupported`.    async fn read_copy(&self, id: &CopyId) -> BackendResult<CopyHistory>;     /// Write the `CopyHistory` object and return its ID.    ///    /// Backends that don't support copy tracking may return    /// `BackendError::Unsupported`.    async fn write_copy(&self, copy: &CopyHistory) -> BackendResult<CopyId>;     /// Find all copy histories that are related to the specified one. This is    /// defined as those that are ancestors of the given specified one, plus    /// their descendants. Children must be returned before parents.    ///    /// It is valid (but wasteful) to include other copy histories, such as    /// siblings, or even completely unrelated copy histories.

Lines 1–500 of 563.