#![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. Load more
#![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 kins impl 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 tags impl 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>(