Skip to main content

gix/repository/
object.rs

1#![allow(clippy::result_large_err)]
2use std::ops::DerefMut;
3
4use gix_hash::ObjectId;
5use gix_object::{Exists, Find, FindExt, Write};
6use gix_odb::{Header, HeaderExt};
7use gix_ref::{
8    transaction::{LogChange, PreviousValue, RefLog},
9    FullName,
10};
11use smallvec::SmallVec;
12
13use crate::repository::{new_commit, new_commit_as};
14use crate::{commit, ext::ObjectIdExt, object, tag, Blob, Commit, Id, Object, Reference, Tag, Tree};
15
16/// Tree editing
17#[cfg(feature = "tree-editor")]
18impl crate::Repository {
19    /// Return an editor for adjusting the tree at `id`.
20    ///
21    /// This can be the [empty tree id](ObjectId::empty_tree) to build a tree from scratch.
22    #[doc(alias = "treebuilder", alias = "git2")]
23    pub fn edit_tree(
24        &self,
25        id: impl Into<ObjectId>,
26    ) -> Result<object::tree::Editor<'_>, crate::repository::edit_tree::Error> {
27        let tree = self.find_tree(id)?;
28        Ok(tree.edit()?)
29    }
30}
31
32/// Find objects of various kins
33impl crate::Repository {
34    /// Find the object with `id` in the object database or return an error if it could not be found.
35    ///
36    /// There are various legitimate reasons for an object to not be present, which is why
37    /// [`try_find_object(…)`][crate::Repository::try_find_object()] might be preferable instead.
38    ///
39    /// # Performance Note
40    ///
41    /// In order to get the kind of the object, is must be fully decoded from storage if it is packed with deltas.
42    /// Loose object could be partially decoded, even though that's not implemented.
43    pub fn find_object(&self, id: impl Into<ObjectId>) -> Result<Object<'_>, object::find::existing::Error> {
44        let id = id.into();
45        if id == ObjectId::empty_tree(self.object_hash()) {
46            return Ok(Object {
47                id,
48                kind: gix_object::Kind::Tree,
49                data: Vec::new(),
50                repo: self,
51            });
52        }
53        let mut buf = self.free_buf();
54        let kind = self.objects.find(&id, &mut buf)?.kind;
55        Ok(Object::from_data(id, kind, buf, self))
56    }
57
58    /// Find a commit with `id` or fail if there was no object or the object wasn't a commit.
59    pub fn find_commit(
60        &self,
61        id: impl Into<ObjectId>,
62    ) -> Result<Commit<'_>, object::find::existing::with_conversion::Error> {
63        Ok(self.find_object(id)?.try_into_commit()?)
64    }
65
66    /// Find a tree with `id` or fail if there was no object or the object wasn't a tree.
67    pub fn find_tree(
68        &self,
69        id: impl Into<ObjectId>,
70    ) -> Result<Tree<'_>, object::find::existing::with_conversion::Error> {
71        Ok(self.find_object(id)?.try_into_tree()?)
72    }
73
74    /// Find an annotated tag with `id` or fail if there was no object or the object wasn't a tag.
75    pub fn find_tag(&self, id: impl Into<ObjectId>) -> Result<Tag<'_>, object::find::existing::with_conversion::Error> {
76        Ok(self.find_object(id)?.try_into_tag()?)
77    }
78
79    /// Find a blob with `id` or fail if there was no object or the object wasn't a blob.
80    pub fn find_blob(
81        &self,
82        id: impl Into<ObjectId>,
83    ) -> Result<Blob<'_>, object::find::existing::with_conversion::Error> {
84        Ok(self.find_object(id)?.try_into_blob()?)
85    }
86
87    /// Obtain information about an object without fully decoding it, or fail if the object doesn't exist.
88    ///
89    /// Note that despite being cheaper than [`Self::find_object()`], there is still some effort traversing delta-chains.
90    /// Also note that for empty trees and blobs, it will always report it to exist in loose objects, even if they don't
91    /// exist or if they exist in a pack.
92    #[doc(alias = "read_header", alias = "git2")]
93    pub fn find_header(&self, id: impl Into<ObjectId>) -> Result<gix_odb::find::Header, object::find::existing::Error> {
94        let id = id.into();
95        if id == ObjectId::empty_tree(self.object_hash()) {
96            return Ok(gix_odb::find::Header::Loose {
97                kind: gix_object::Kind::Tree,
98                size: 0,
99            });
100        }
101        self.objects.header(id)
102    }
103
104    /// Return `true` if `id` exists in the object database.
105    ///
106    /// # Performance
107    ///
108    /// This method can be slow if the underlying [object database](crate::Repository::objects) has
109    /// an unsuitable [RefreshMode](gix_odb::store::RefreshMode) and `id` is not likely to exist.
110    /// Use [`repo.objects.refresh_never()`](gix_odb::store::Handle::refresh_never) to avoid expensive
111    /// IO-bound refreshes if an object wasn't found.
112    #[doc(alias = "exists", alias = "git2")]
113    pub fn has_object(&self, id: impl AsRef<gix_hash::oid>) -> bool {
114        let id = id.as_ref();
115        if id.to_owned().is_empty_tree() {
116            true
117        } else {
118            self.objects.exists(id)
119        }
120    }
121
122    /// Obtain information about an object without fully decoding it, or `None` if the object doesn't exist.
123    ///
124    /// Note that despite being cheaper than [`Self::try_find_object()`], there is still some effort traversing delta-chains.
125    pub fn try_find_header(
126        &self,
127        id: impl Into<ObjectId>,
128    ) -> Result<Option<gix_odb::find::Header>, object::find::Error> {
129        let id = id.into();
130        if id == ObjectId::empty_tree(self.object_hash()) {
131            return Ok(Some(gix_odb::find::Header::Loose {
132                kind: gix_object::Kind::Tree,
133                size: 0,
134            }));
135        }
136        self.objects.try_header(&id).map_err(Into::into)
137    }
138
139    /// Try to find the object with `id` or return `None` if it wasn't found.
140    pub fn try_find_object(&self, id: impl Into<ObjectId>) -> Result<Option<Object<'_>>, object::find::Error> {
141        let id = id.into();
142        if id == ObjectId::empty_tree(self.object_hash()) {
143            return Ok(Some(Object {
144                id,
145                kind: gix_object::Kind::Tree,
146                data: Vec::new(),
147                repo: self,
148            }));
149        }
150
151        let mut buf = self.free_buf();
152        match self.objects.try_find(&id, &mut buf)? {
153            Some(obj) => {
154                let kind = obj.kind;
155                Ok(Some(Object::from_data(id, kind, buf, self)))
156            }
157            None => Ok(None),
158        }
159    }
160}
161
162/// Write objects of any type.
163impl crate::Repository {
164    /// Write the given object into the object database and return its object id.
165    ///
166    /// Note that we hash the object in memory to avoid storing objects that are already present. That way,
167    /// we avoid writing duplicate objects using slow disks that will eventually have to be garbage collected.
168    pub fn write_object(&self, object: impl gix_object::WriteTo) -> Result<Id<'_>, object::write::Error> {
169        let mut buf = self.empty_reusable_buffer();
170        object
171            .write_to(buf.deref_mut())
172            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)?;
173
174        self.write_object_inner(&buf, object.kind())
175    }
176
177    fn write_object_inner(&self, buf: &[u8], kind: gix_object::Kind) -> Result<Id<'_>, object::write::Error> {
178        let oid = gix_object::compute_hash(self.object_hash(), kind, buf)
179            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
180        if self.objects.exists(&oid) {
181            return Ok(oid.attach(self));
182        }
183
184        self.objects
185            .write_buf(kind, buf)
186            .map(|oid| oid.attach(self))
187            .map_err(Into::into)
188    }
189
190    /// Write a blob from the given `bytes`.
191    ///
192    /// We avoid writing duplicate objects to slow disks that will eventually have to be garbage collected by
193    /// pre-hashing the data, and checking if the object is already present.
194    pub fn write_blob(&self, bytes: impl AsRef<[u8]>) -> Result<Id<'_>, object::write::Error> {
195        let bytes = bytes.as_ref();
196        let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, bytes)
197            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
198        if self.objects.exists(&oid) {
199            return Ok(oid.attach(self));
200        }
201        self.objects
202            .write_buf(gix_object::Kind::Blob, bytes)
203            .map_err(Into::into)
204            .map(|oid| oid.attach(self))
205    }
206
207    /// Write a blob from the given `Read` implementation.
208    ///
209    /// Note that we hash the object in memory to avoid storing objects that are already present. That way,
210    /// we avoid writing duplicate objects using slow disks that will eventually have to be garbage collected.
211    ///
212    /// If that is prohibitive, use the object database directly.
213    pub fn write_blob_stream(&self, mut bytes: impl std::io::Read) -> Result<Id<'_>, object::write::Error> {
214        let mut buf = self.empty_reusable_buffer();
215        std::io::copy(&mut bytes, buf.deref_mut())
216            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
217
218        self.write_blob_stream_inner(&buf)
219    }
220
221    fn write_blob_stream_inner(&self, buf: &[u8]) -> Result<Id<'_>, object::write::Error> {
222        let oid = gix_object::compute_hash(self.object_hash(), gix_object::Kind::Blob, buf)
223            .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
224        if self.objects.exists(&oid) {
225            return Ok(oid.attach(self));
226        }
227
228        self.objects
229            .write_buf(gix_object::Kind::Blob, buf)
230            .map_err(Into::into)
231            .map(|oid| oid.attach(self))
232    }
233}
234
235/// Create commits and tags
236impl crate::Repository {
237    /// Create a tag reference named `name` (without `refs/tags/` prefix) pointing to a newly created tag object
238    /// which in turn points to `target` and return the newly created reference.
239    ///
240    /// It will be created with `constraint` which is most commonly to [only create it](PreviousValue::MustNotExist)
241    /// or to [force overwriting a possibly existing tag](PreviousValue::Any).
242    pub fn tag(
243        &self,
244        name: impl AsRef<str>,
245        target: impl AsRef<gix_hash::oid>,
246        target_kind: gix_object::Kind,
247        tagger: Option<gix_actor::SignatureRef<'_>>,
248        message: impl AsRef<str>,
249        constraint: PreviousValue,
250    ) -> Result<Reference<'_>, tag::Error> {
251        let tag = gix_object::Tag {
252            target: target.as_ref().into(),
253            target_kind,
254            name: name.as_ref().into(),
255            tagger: tagger.map(|t| t.to_owned()).transpose()?,
256            message: message.as_ref().into(),
257            pgp_signature: None,
258        };
259        let tag_id = self.write_object(&tag)?;
260        self.tag_reference(name, tag_id, constraint).map_err(Into::into)
261    }
262
263    /// Similar to [`commit(…)`](crate::Repository::commit()), but allows to create the commit with `committer` and `author` specified.
264    ///
265    /// This forces setting the commit time and author time by hand. Note that typically, committer and author are the same.
266    pub fn commit_as<'a, 'c, Name, E>(
267        &self,
268        committer: impl Into<gix_actor::SignatureRef<'c>>,
269        author: impl Into<gix_actor::SignatureRef<'a>>,
270        reference: Name,
271        message: impl AsRef<str>,
272        tree: impl Into<ObjectId>,
273        parents: impl IntoIterator<Item = impl Into<ObjectId>>,
274    ) -> Result<Id<'_>, commit::Error>
275    where
276        Name: TryInto<FullName, Error = E>,
277        commit::Error: From<E>,
278    {
279        self.commit_as_inner(
280            committer.into(),
281            author.into(),
282            reference.try_into()?,
283            message.as_ref(),
284            tree.into(),
285            parents.into_iter().map(Into::into).collect(),
286        )
287    }
288
289    fn commit_as_inner(
290        &self,
291        committer: gix_actor::SignatureRef<'_>,
292        author: gix_actor::SignatureRef<'_>,
293        reference: FullName,
294        message: &str,
295        tree: ObjectId,
296        parents: SmallVec<[ObjectId; 1]>,
297    ) -> Result<Id<'_>, commit::Error> {
298        use gix_ref::{
299            transaction::{Change, RefEdit},
300            Target,
301        };
302
303        // TODO: possibly use CommitRef to save a few allocations (but will have to allocate for object ids anyway.
304        //       This can be made vastly more efficient though if we wanted to, so we lie in the API
305        let commit = gix_object::Commit {
306            message: message.into(),
307            tree,
308            author: author.into(),
309            committer: committer.into(),
310            encoding: None,
311            parents,
312            extra_headers: Default::default(),
313        };
314
315        let commit_id = self.write_object(&commit)?;
316        self.edit_references_as(
317            Some(RefEdit {
318                change: Change::Update {
319                    log: LogChange {
320                        mode: RefLog::AndReference,
321                        force_create_reflog: false,
322                        message: crate::reference::log::message(
323                            "commit",
324                            commit.message.as_ref(),
325                            commit.parents.len(),
326                        ),
327                    },
328                    expected: match commit.parents.first().map(|p| Target::Object(*p)) {
329                        Some(previous) => {
330                            if reference.as_bstr() == "HEAD" {
331                                PreviousValue::MustExistAndMatch(previous)
332                            } else {
333                                PreviousValue::ExistingMustMatch(previous)
334                            }
335                        }
336                        None => PreviousValue::MustNotExist,
337                    },
338                    new: Target::Object(commit_id.inner),
339                },
340                name: reference,
341                deref: true,
342            }),
343            Some(committer),
344        )?;
345        Ok(commit_id)
346    }
347
348    /// Create a new commit object with `message` referring to `tree` with `parents`, and point `reference`
349    /// to it. The commit is written without message encoding field, which can be assumed to be UTF-8.
350    /// `author` and `committer` fields are pre-set from the configuration, which can be altered
351    /// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.
352    ///
353    /// `reference` will be created if it doesn't exist, and can be `"HEAD"` to automatically write-through to the symbolic reference
354    /// that `HEAD` points to if it is not detached. For this reason, detached head states cannot be created unless the `HEAD` is detached
355    /// already. The reflog will be written as canonical git would do, like `<operation> (<detail>): <summary>`.
356    ///
357    /// The first parent id in `parents` is expected to be the current target of `reference` and the operation will fail if it is not.
358    /// If there is no parent, the `reference` is expected to not exist yet.
359    ///
360    /// The method fails immediately if a `reference` lock can't be acquired.
361    ///
362    /// ### Writing a commit without `reference` update
363    ///
364    /// If the reference shouldn't be updated, use [`Self::write_object()`] along with a newly created [`crate::objs::Object`] whose fields
365    /// can be fully defined.
366    pub fn commit<Name, E>(
367        &self,
368        reference: Name,
369        message: impl AsRef<str>,
370        tree: impl Into<ObjectId>,
371        parents: impl IntoIterator<Item = impl Into<ObjectId>>,
372    ) -> Result<Id<'_>, commit::Error>
373    where
374        Name: TryInto<FullName, Error = E>,
375        commit::Error: From<E>,
376    {
377        let author = self.author().ok_or(commit::Error::AuthorMissing)??;
378        let committer = self.committer().ok_or(commit::Error::CommitterMissing)??;
379        self.commit_as(committer, author, reference, message, tree, parents)
380    }
381
382    /// Create a new commit object with `message` referring to `tree` with `parents`, and write it to the object database.
383    /// Do not, however, update any references.
384    ///
385    /// The commit is created without message encoding field, which can be assumed to be UTF-8.
386    /// `author` and `committer` fields are pre-set from the configuration, which can be altered
387    /// [temporarily](crate::Repository::config_snapshot_mut()) before the call if required.
388    pub fn new_commit(
389        &self,
390        message: impl AsRef<str>,
391        tree: impl Into<ObjectId>,
392        parents: impl IntoIterator<Item = impl Into<ObjectId>>,
393    ) -> Result<Commit<'_>, new_commit::Error> {
394        let author = self.author().ok_or(new_commit::Error::AuthorMissing)??;
395        let committer = self.committer().ok_or(new_commit::Error::CommitterMissing)??;
396        Ok(self.new_commit_as(committer, author, message, tree, parents)?)
397    }
398
399    /// Create a nwe commit object with `message` referring to `tree` with `parents`, using the specified
400    /// `committer` and `author`, and write it to the object database. Do not, however, update any references.
401    ///
402    /// This forces setting the commit time and author time by hand. Note that typically, committer and author are the same.
403    /// The commit is created without message encoding field, which can be assumed to be UTF-8.
404    pub fn new_commit_as<'a, 'c>(
405        &self,
406        committer: impl Into<gix_actor::SignatureRef<'c>>,
407        author: impl Into<gix_actor::SignatureRef<'a>>,
408        message: impl AsRef<str>,
409        tree: impl Into<ObjectId>,
410        parents: impl IntoIterator<Item = impl Into<ObjectId>>,
411    ) -> Result<Commit<'_>, new_commit_as::Error> {
412        let commit = gix_object::Commit {
413            message: message.as_ref().into(),
414            tree: tree.into(),
415            author: author.into().into(),
416            committer: committer.into().into(),
417            encoding: None,
418            parents: parents.into_iter().map(Into::into).collect(),
419            extra_headers: Default::default(),
420        };
421        let id = self.write_object(commit)?;
422        Ok(id.object()?.into_commit())
423    }
424
425    /// Return an empty tree object, suitable for [getting changes](Tree::changes()).
426    ///
427    /// Note that the returned object is special and doesn't necessarily physically exist in the object database.
428    /// This means that this object can be used in an uninitialized, empty repository which would report to have no objects at all.
429    pub fn empty_tree(&self) -> Tree<'_> {
430        self.find_object(ObjectId::empty_tree(self.object_hash()))
431            .expect("always present")
432            .into_tree()
433    }
434
435    /// Return an empty blob object.
436    ///
437    /// Note that the returned object is special and doesn't necessarily physically exist in the object database.
438    /// This means that this object can be used in an uninitialized, empty repository which would report to have no objects at all.
439    pub fn empty_blob(&self) -> Blob<'_> {
440        Blob {
441            id: self.object_hash().empty_blob(),
442            data: Vec::new(),
443            repo: self,
444        }
445    }
446}