542 lines · 22.1 KB · Rust
RawDownload
#![allow(clippy::result_large_err)]use std::ops::DerefMut; use gix_hash::ObjectId;use gix_object::{Exists, Find, FindExt, Write};use gix_odb::{Header, HeaderExt};use gix_ref::{    FullName,    transaction::{LogChange, PreviousValue, RefLog},};use smallvec::SmallVec; use crate::repository::{new_commit, new_commit_as};use crate::{Blob, Commit, Id, Object, Reference, Tag, Tree, commit, ext::ObjectIdExt, object, tag}; /// Tree editing#[cfg(feature = "tree-editor")]impl crate::Repository {    /// Return an editor for adjusting the tree at `id`.    ///    /// This can be the [empty tree id](ObjectId::empty_tree) to build a tree from scratch.    #[doc(alias = "treebuilder", alias = "git2")]    pub fn edit_tree(        &self,        id: impl Into<ObjectId>,    ) -> Result<object::tree::Editor<'_>, crate::repository::edit_tree::Error> {        let tree = self.find_tree(id)?;        Ok(tree.edit()?)    }} /// Find objects of various kinsimpl crate::Repository {    /// Find the object with `id` in the object database or return an error if it could not be found.    ///    /// There are various legitimate reasons for an object to not be present, which is why    /// [`try_find_object(…)`][crate::Repository::try_find_object()] might be preferable instead.    ///    /// # Performance Note    ///    /// In order to get the kind of the object, is must be fully decoded from storage if it is packed with deltas.    /// Loose object could be partially decoded, even though that's not implemented.    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;    /// let object = repo.find_object(repo.head_id()?)?;    ///    /// assert_eq!(object.kind, gix::objs::Kind::Commit);    /// assert_eq!(object.into_commit().message_raw()?, "c2\n");    /// # Ok(()) }    /// ```    pub fn find_object(&self, id: impl Into<ObjectId>) -> Result<Object<'_>, object::find::existing::Error> {        let id = id.into();        if id == ObjectId::empty_tree(self.object_hash()) {            return Ok(Object {                id,                kind: gix_object::Kind::Tree,                data: Vec::new(),                repo: self,            });        }        let mut buf = self.free_buf();        let kind = self.objects.find(&id, &mut buf)?.kind;        Ok(Object::from_data(id, kind, buf, self))    }     /// Find a commit with `id` or fail if there was no object or the object wasn't a commit.    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;    /// let commit = repo.find_commit(repo.head_id()?)?;    ///    /// assert_eq!(commit.message_raw()?, "c2\n");    /// # Ok(()) }    /// ```    pub fn find_commit(        &self,        id: impl Into<ObjectId>,    ) -> Result<Commit<'_>, object::find::existing::with_conversion::Error> {        Ok(self.find_object(id)?.try_into_commit()?)    }     /// Find a tree with `id` or fail if there was no object or the object wasn't a tree.    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;    /// let tree = repo.find_tree(repo.head_tree_id()?)?;    ///    /// assert_eq!(tree.find_entry("this").expect("present").filename(), "this");    /// # Ok(()) }    /// ```    pub fn find_tree(        &self,        id: impl Into<ObjectId>,    ) -> Result<Tree<'_>, object::find::existing::with_conversion::Error> {        Ok(self.find_object(id)?.try_into_tree()?)    }     /// Find an annotated tag with `id` or fail if there was no object or the object wasn't a tag.    pub fn find_tag(&self, id: impl Into<ObjectId>) -> Result<Tag<'_>, object::find::existing::with_conversion::Error> {        Ok(self.find_object(id)?.try_into_tag()?)    }     /// Find a blob with `id` or fail if there was no object or the object wasn't a blob.    pub fn find_blob(        &self,        id: impl Into<ObjectId>,    ) -> Result<Blob<'_>, object::find::existing::with_conversion::Error> {        Ok(self.find_object(id)?.try_into_blob()?)    }     /// Obtain information about an object without fully decoding it, or fail if the object doesn't exist.    ///    /// Note that despite being cheaper than [`Self::find_object()`], there is still some effort traversing delta-chains.    /// Also note that for empty trees and blobs, it will always report it to exist in loose objects, even if they don't    /// exist or if they exist in a pack.    #[doc(alias = "read_header", alias = "git2")]    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;    /// let header = repo.find_header(repo.head_id()?)?;    ///    /// assert_eq!(header.kind(), gix::objs::Kind::Commit);    /// # Ok(()) }    /// ```    pub fn find_header(&self, id: impl Into<ObjectId>) -> Result<gix_odb::find::Header, object::find::existing::Error> {        let id = id.into();        if id == ObjectId::empty_tree(self.object_hash()) {            return Ok(gix_odb::find::Header::Loose {                kind: gix_object::Kind::Tree,                size: 0,            });        }        self.objects.header(id)    }     /// Return `true` if `id` exists in the object database.    ///    /// # Performance    ///    /// This method can be slow if the underlying [object database](crate::Repository::objects) has    /// an unsuitable [RefreshMode](gix_odb::store::RefreshMode) and `id` is not likely to exist.    /// Use [`repo.objects.refresh_never()`](gix_odb::store::Handle::refresh_never) to avoid expensive    /// IO-bound refreshes if an object wasn't found.    #[doc(alias = "exists", alias = "git2")]    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;    /// assert!(repo.has_object(repo.head_id()?));    /// # Ok(()) }    /// ```    pub fn has_object(&self, id: impl AsRef<gix_hash::oid>) -> bool {        let id = id.as_ref();        if id.to_owned().is_empty_tree() {            true        } else {            self.objects.exists(id)        }    }     /// Obtain information about an object without fully decoding it, or `None` if the object doesn't exist.    ///    /// Note that despite being cheaper than [`Self::try_find_object()`], there is still some effort traversing delta-chains.    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;    /// let header = repo.try_find_header(repo.head_id()?)?.expect("present");    ///    /// assert_eq!(header.kind(), gix::objs::Kind::Commit);    /// # Ok(()) }    /// ```    pub fn try_find_header(        &self,        id: impl Into<ObjectId>,    ) -> Result<Option<gix_odb::find::Header>, object::find::Error> {        let id = id.into();        if id == ObjectId::empty_tree(self.object_hash()) {            return Ok(Some(gix_odb::find::Header::Loose {                kind: gix_object::Kind::Tree,                size: 0,            }));        }        self.objects.try_header(&id).map_err(Into::into)    }     /// Try to find the object with `id` or return `None` if it wasn't found.    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;    /// let object = repo.try_find_object(repo.head_id()?)?.expect("present");    ///    /// assert_eq!(object.kind, gix::objs::Kind::Commit);    /// # Ok(()) }    /// ```    pub fn try_find_object(&self, id: impl Into<ObjectId>) -> Result<Option<Object<'_>>, object::find::Error> {        let id = id.into();        if id == ObjectId::empty_tree(self.object_hash()) {            return Ok(Some(Object {                id,                kind: gix_object::Kind::Tree,                data: Vec::new(),                repo: self,            }));        }         let mut buf = self.free_buf();        match self.objects.try_find(&id, &mut buf)? {            Some(obj) => {                let kind = obj.kind;                Ok(Some(Object::from_data(id, kind, buf, self)))            }            None => Ok(None),        }    }} /// Write objects of any type.impl crate::Repository {    /// Write the given object into the object database and return its object id.    ///    /// Note that we hash the object in memory to avoid storing objects that are already present. That way,    /// we avoid writing duplicate objects using slow disks that will eventually have to be garbage collected.    pub fn write_object(&self, object: impl gix_object::WriteTo) -> Result<Id<'_>, object::write::Error> {        let mut buf = self.empty_reusable_buffer();        object            .write_to(buf.deref_mut())            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)?;         self.write_object_inner(&buf, object.kind())    }     fn write_object_inner(&self, buf: &[u8], kind: gix_object::Kind) -> Result<Id<'_>, object::write::Error> {        let oid = gix_object::compute_hash(self.object_hash(), kind, buf)            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;        if self.objects.exists(&oid) {            return Ok(oid.attach(self));        }         self.objects            .write_buf_with_known_id(kind, buf, oid)            .map(|oid| oid.attach(self))            .map_err(Into::into)    }     /// Write a blob from the given `bytes`.    ///    /// We avoid writing duplicate objects to slow disks that will eventually have to be garbage collected by    /// pre-hashing the data, and checking if the object is already present.    ///    /// # Examples    ///    /// ```    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }    /// # let dir = doctest::tempdir()?;    /// let repo = gix::init_bare(dir.path())?;    /// let blob_id = repo.write_blob(b"hello world")?;    ///    /// assert_eq!(repo.find_blob(blob_id)?.data, b"hello world");    /// # Ok(()) }    /// ```    pub fn write_blob(&self, bytes: impl AsRef<[u8]>) -> Result<Id<'_>, object::write::Error> {        let bytes = bytes.as_ref();        let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, bytes)            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;        if self.objects.exists(&oid) {            return Ok(oid.attach(self));        }        self.objects            .write_buf_with_known_id(gix_object::Kind::Blob, bytes, oid)            .map_err(Into::into)            .map(|oid| oid.attach(self))    }     /// Write a blob from the given `Read` implementation.    ///    /// Note that we hash the object in memory to avoid storing objects that are already present. That way,    /// we avoid writing duplicate objects using slow disks that will eventually have to be garbage collected.    ///    /// If that is prohibitive, use the object database directly.    pub fn write_blob_stream(&self, mut bytes: impl std::io::Read) -> Result<Id<'_>, object::write::Error> {        let mut buf = self.empty_reusable_buffer();        std::io::copy(&mut bytes, buf.deref_mut())            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;         self.write_blob_stream_inner(&buf)    }     fn write_blob_stream_inner(&self, buf: &[u8]) -> Result<Id<'_>, object::write::Error> {        let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, buf)            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;        if self.objects.exists(&oid) {            return Ok(oid.attach(self));        }         self.objects            .write_buf_with_known_id(gix_object::Kind::Blob, buf, oid)            .map_err(Into::into)            .map(|oid| oid.attach(self))    }} /// Create commits and tagsimpl crate::Repository {    /// Create a tag reference named `name` (without `refs/tags/` prefix) pointing to a newly created tag object    /// which in turn points to `target` and return the newly created reference.    ///    /// It will be created with `constraint` which is most commonly to [only create it](PreviousValue::MustNotExist)    /// or to [force overwriting a possibly existing tag](PreviousValue::Any).    pub fn tag(        &self,        name: impl AsRef<str>,        target: impl AsRef<gix_hash::oid>,        target_kind: gix_object::Kind,        tagger: Option<gix_actor::SignatureRef<'_>>,        message: impl AsRef<str>,        constraint: PreviousValue,    ) -> Result<Reference<'_>, tag::Error> {        let tag = gix_object::Tag {            target: target.as_ref().into(),            target_kind,            name: name.as_ref().into(),            tagger: tagger.map(|t| t.to_owned()).transpose()?,            message: message.as_ref().into(),            pgp_signature: None,        };        let tag_id = self.write_object(&tag)?;        self.tag_reference(name, tag_id, constraint).map_err(Into::into)    }     /// Similar to [`commit(…)`](crate::Repository::commit()), but allows to create the commit with `committer` and `author` specified.    ///    /// This forces setting the commit time and author time by hand. Note that typically, committer and author are the same.    pub fn commit_as<'a, 'c, Name, E>(        &self,        committer: impl Into<gix_actor::SignatureRef<'c>>,        author: impl Into<gix_actor::SignatureRef<'a>>,        reference: Name,        message: impl AsRef<str>,        tree: impl Into<ObjectId>,        parents: impl IntoIterator<Item = impl Into<ObjectId>>,    ) -> Result<Id<'_>, commit::Error>    where        Name: TryInto<FullName, Error = E>,        commit::Error: From<E>,    {        self.commit_as_inner(            committer.into(),            author.into(),            reference.try_into()?,            message.as_ref(),            tree.into(),            parents.into_iter().map(Into::into).collect(),        )    }     fn commit_as_inner(        &self,        committer: gix_actor::SignatureRef<'_>,        author: gix_actor::SignatureRef<'_>,        reference: FullName,        message: &str,        tree: ObjectId,        parents: SmallVec<[ObjectId; 1]>,    ) -> Result<Id<'_>, commit::Error> {        use gix_ref::{            Target,            transaction::{Change, RefEdit},        };         // TODO: possibly use CommitRef to save a few allocations (but will have to allocate for object ids anyway.        //       This can be made vastly more efficient though if we wanted to, so we lie in the API        let commit = gix_object::Commit {            message: message.into(),            tree,            author: author.into(),            committer: committer.into(),            encoding: None,            parents,            extra_headers: Default::default(),        };         let commit_id = self.write_object(&commit)?;        self.edit_references_as(            Some(RefEdit {                change: Change::Update {                    log: LogChange {                        mode: RefLog::AndReference,                        force_create_reflog: false,                        message: crate::reference::log::message(                            "commit",                            commit.message.as_ref(),                            commit.parents.len(),                        ),                    },                    expected: match commit.parents.first().map(|p| Target::Object(*p)) {                        Some(previous) => {                            if reference.as_bstr() == "HEAD" {                                PreviousValue::MustExistAndMatch(previous)                            } else {                                PreviousValue::ExistingMustMatch(previous)                            }                        }                        None => PreviousValue::MustNotExist,                    },                    new: Target::Object(commit_id.inner),                },                name: reference,                deref: true,            }),            Some(committer),        )?;        Ok(commit_id)    }     /// Create a new commit object with `message` referring to `tree` with `parents`, and point `reference`    /// to it. The commit is written without message encoding field, which can be assumed to be UTF-8.    /// `author` and `committer` fields are pre-set from the configuration, which can be altered    /// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.    ///    /// `reference` will be created if it doesn't exist, and can be `"HEAD"` to automatically write-through to the symbolic reference    /// that `HEAD` points to if it is not detached. For this reason, detached head states cannot be created unless the `HEAD` is detached    /// already. The reflog will be written as canonical git would do, like `<operation> (<detail>): <summary>`.    ///    /// The first parent id in `parents` is expected to be the current target of `reference` and the operation will fail if it is not.    /// If there is no parent, the `reference` is expected to not exist yet.    ///    /// The method fails immediately if a `reference` lock can't be acquired.    ///    /// ### Writing a commit without `reference` update    ///    /// If the reference shouldn't be updated, use [`Self::write_object()`] along with a newly created [`crate::objs::Object`] whose fields    /// can be fully defined.    pub fn commit<Name, E>(        &self,        reference: Name,        message: impl AsRef<str>,        tree: impl Into<ObjectId>,        parents: impl IntoIterator<Item = impl Into<ObjectId>>,    ) -> Result<Id<'_>, commit::Error>    where        Name: TryInto<FullName, Error = E>,        commit::Error: From<E>,    {        let author = self.author().ok_or(commit::Error::AuthorMissing)??;        let committer = self.committer().ok_or(commit::Error::CommitterMissing)??;        self.commit_as(committer, author, reference, message, tree, parents)    }     /// Create a new commit object with `message` referring to `tree` with `parents`, and write it to the object database.    /// Do not, however, update any references.    ///    /// The commit is created without message encoding field, which can be assumed to be UTF-8.    /// `author` and `committer` fields are pre-set from the configuration, which can be altered    /// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.    pub fn new_commit(        &self,        message: impl AsRef<str>,        tree: impl Into<ObjectId>,        parents: impl IntoIterator<Item = impl Into<ObjectId>>,    ) -> Result<Commit<'_>, new_commit::Error> {        let author = self.author().ok_or(new_commit::Error::AuthorMissing)??;        let committer = self.committer().ok_or(new_commit::Error::CommitterMissing)??;        Ok(self.new_commit_as(committer, author, message, tree, parents)?)    }     /// Create a nwe commit object with `message` referring to `tree` with `parents`, using the specified    /// `committer` and `author`, and write it to the object database. Do not, however, update any references.    ///    /// This forces setting the commit time and author time by hand. Note that typically, committer and author are the same.    /// The commit is created without message encoding field, which can be assumed to be UTF-8.    pub fn new_commit_as<'a, 'c>(

Lines 1–500 of 542.