Skip to main content

gix_object/
lib.rs

1//! This crate provides types for [read-only git objects][crate::ObjectRef] backed by bytes provided in git's serialization format
2//! as well as [mutable versions][Object] of these. Both types of objects can be encoded.
3//!
4//! ## Decode Borrowed Objects
5//!
6//! ```
7//! let object = gix_object::ObjectRef::from_loose(b"blob 5\0hello", gix_hash::Kind::Sha1).unwrap();
8//! let blob = object.as_blob().unwrap();
9//!
10//! assert_eq!(blob.data, b"hello");
11//! assert_eq!(object.kind(), gix_object::Kind::Blob);
12//! ```
13//!
14//! ## Mutate And Encode Owned Objects
15//!
16//! ```
17//! use gix_object::WriteTo;
18//!
19//! let object = gix_object::ObjectRef::from_loose(b"blob 5\0hello", gix_hash::Kind::Sha1)
20//!     .unwrap()
21//!     .into_owned()
22//!     .unwrap();
23//! let mut blob = object.into_blob();
24//! blob.data.extend_from_slice(b" world");
25//!
26//! let mut out = Vec::new();
27//! blob.write_to(&mut out).unwrap();
28//! assert_eq!(out, b"hello world");
29//! assert_eq!(blob.loose_header().as_slice(), b"blob 11\0");
30//! ```
31//! ## Feature Flags
32#![cfg_attr(
33    all(doc, feature = "document-features"),
34    doc = ::document_features::document_features!()
35)]
36#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))]
37#![deny(missing_docs)]
38#![forbid(unsafe_code)]
39
40use gix_error::Result;
41use std::borrow::Cow;
42
43/// For convenience to allow using `bstr` without adding it to own cargo manifest.
44pub use bstr;
45use bstr::{BStr, BString, ByteSlice};
46/// For convenience to allow using `gix-date` without adding it to own cargo manifest.
47pub use gix_date as date;
48use smallvec::SmallVec;
49
50///
51pub mod commit;
52mod object;
53/// Cryptographic signature discovery and, with the `signature` feature, external signing and verification.
54pub mod signature;
55///
56pub mod tag;
57///
58pub mod tree;
59
60mod blob;
61///
62pub mod data;
63
64///
65pub mod find;
66
67mod traits;
68pub use traits::{Exists, Find, FindExt, FindObjectOrHeader, Header as FindHeader, HeaderExt, Write, WriteTo};
69
70pub mod encode;
71pub(crate) mod parse;
72
73///
74pub mod kind;
75
76/// The four types of objects that git differentiates.
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
79#[expect(missing_docs)]
80pub enum Kind {
81    Tree,
82    Blob,
83    Commit,
84    Tag,
85}
86/// A chunk of any [`data`](BlobRef::data).
87#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct BlobRef<'a> {
90    /// The bytes themselves.
91    pub data: &'a [u8],
92}
93
94/// A mutable chunk of any [`data`](Blob::data).
95#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub struct Blob {
98    /// The data itself.
99    pub data: Vec<u8>,
100}
101
102/// A git commit parsed using [`from_bytes()`](CommitRef::from_bytes()).
103///
104/// A commit encapsulates information about a point in time at which the state of the repository is recorded, usually after a
105/// change which is documented in the commit `message`.
106#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
108pub struct CommitRef<'a> {
109    /// HEX hash of tree object we point to.
110    ///
111    /// Use [`tree()`](CommitRef::tree()) to obtain a decoded version of it.
112    #[cfg_attr(feature = "serde", serde(borrow))]
113    pub tree: &'a BStr,
114    /// HEX hash of each parent commit. Empty for first commit in repository.
115    pub parents: SmallVec<[&'a BStr; 1]>,
116    /// The raw author header value as encountered during parsing.
117    ///
118    /// Use the [`author()`](CommitRef::author()) method to obtain a parsed version of it.
119    #[cfg_attr(feature = "serde", serde(borrow))]
120    pub author: &'a BStr,
121    /// The raw committer header value as encountered during parsing.
122    ///
123    /// Use the [`committer()`](CommitRef::committer()) method to obtain a parsed version of it.
124    #[cfg_attr(feature = "serde", serde(borrow))]
125    pub committer: &'a BStr,
126    /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493).
127    pub encoding: Option<&'a BStr>,
128    /// The commit message documenting the change.
129    pub message: &'a BStr,
130    /// Extra header fields, in order of them being encountered, made accessible with the iterator returned by [`extra_headers()`](CommitRef::extra_headers()).
131    pub extra_headers: Vec<(&'a BStr, Cow<'a, BStr>)>,
132}
133
134/// Like [`CommitRef`], but as `Iterator` to support (up to) entirely allocation free parsing.
135/// It's particularly useful to traverse the commit graph without ever allocating arrays for parents.
136#[derive(Copy, Clone)]
137pub struct CommitRefIter<'a> {
138    data: &'a [u8],
139    state: commit::ref_iter::State,
140    hash_kind: gix_hash::Kind,
141}
142
143/// A mutable git commit, representing an annotated state of a working tree along with a reference to its historical commits.
144#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146pub struct Commit {
147    /// The hash of recorded working tree state.
148    pub tree: gix_hash::ObjectId,
149    /// Hash of each parent commit. Empty for the first commit in repository.
150    pub parents: SmallVec<[gix_hash::ObjectId; 1]>,
151    /// Who wrote this commit.
152    pub author: gix_actor::Signature,
153    /// Who committed this commit.
154    ///
155    /// This may be different from the `author` in case the author couldn't write to the repository themselves and
156    /// is commonly encountered with contributed commits.
157    pub committer: gix_actor::Signature,
158    /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493).
159    pub encoding: Option<BString>,
160    /// The commit message documenting the change.
161    pub message: BString,
162    /// Extra header fields, in order of them being encountered, made accessible with the iterator returned
163    /// by [`extra_headers()`](Commit::extra_headers()).
164    pub extra_headers: Vec<(BString, BString)>,
165}
166
167/// Represents a git tag, commonly indicating a software release.
168#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170pub struct TagRef<'a> {
171    /// The hash in hexadecimal being the object this tag points to. Use [`target()`](TagRef::target()) to obtain a byte representation.
172    #[cfg_attr(feature = "serde", serde(borrow))]
173    pub target: &'a BStr,
174    /// The kind of object that `target` points to.
175    pub target_kind: Kind,
176    /// The name of the tag, e.g. "v1.0".
177    pub name: &'a BStr,
178    /// The raw tagger header value as encountered during parsing.
179    ///
180    /// Use the [`tagger()`](TagRef::tagger()) method to obtain a parsed version of it.
181    #[cfg_attr(feature = "serde", serde(borrow))]
182    pub tagger: Option<&'a BStr>,
183    /// The message describing this release.
184    pub message: &'a BStr,
185    /// Any Git-supported in-body cryptographic signature.
186    ///
187    /// Use [`signature()`](TagRef::signature()) to also obtain its detected format.
188    pub signature: Option<&'a BStr>,
189}
190
191/// Like [`TagRef`], but as `Iterator` to support entirely allocation free parsing.
192/// It's particularly useful to dereference only the target chain.
193#[derive(Copy, Clone)]
194pub struct TagRefIter<'a> {
195    data: &'a [u8],
196    state: tag::ref_iter::State,
197    hash_kind: gix_hash::Kind,
198}
199
200/// A mutable git tag.
201#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub struct Tag {
204    /// The hash this tag is pointing to.
205    pub target: gix_hash::ObjectId,
206    /// The kind of object this tag is pointing to.
207    pub target_kind: Kind,
208    /// The name of the tag, e.g. "v1.0".
209    pub name: BString,
210    /// The tags author.
211    pub tagger: Option<gix_actor::Signature>,
212    /// The message describing the tag.
213    pub message: BString,
214    /// Any Git-supported in-body cryptographic signature.
215    ///
216    /// Use [`signature()`](Tag::signature()) to also obtain its detected format.
217    pub signature: Option<BString>,
218}
219
220/// Immutable objects are read-only structures referencing most data from [a byte slice](ObjectRef::from_bytes()).
221///
222/// Immutable objects are expected to be deserialized from bytes that acts as backing store, and they
223/// cannot be mutated or serialized. Instead, one will [convert](ObjectRef::into_owned()) them into their [`mutable`](Object) counterparts
224/// which support mutation and serialization.
225///
226/// An `ObjectRef` is representing [`Trees`](TreeRef), [`Blobs`](BlobRef), [`Commits`](CommitRef), or [`Tags`](TagRef).
227#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
228#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
229#[expect(missing_docs)]
230pub enum ObjectRef<'a> {
231    #[cfg_attr(feature = "serde", serde(borrow))]
232    Tree(TreeRef<'a>),
233    Blob(BlobRef<'a>),
234    Commit(CommitRef<'a>),
235    Tag(TagRef<'a>),
236}
237
238/// Mutable objects with each field being separately allocated and changeable.
239///
240/// Mutable objects are Commits, Trees, Blobs and Tags that can be changed and serialized.
241///
242/// They either created using object [construction](Object) or by [deserializing existing objects](ObjectRef::from_bytes())
243/// and converting these [into mutable copies](ObjectRef::into_owned()) for adjustments.
244///
245/// An `Object` is representing [`Trees`](Tree), [`Blobs`](Blob), [`Commits`](Commit), or [`Tags`](Tag).
246#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
247#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
248#[expect(missing_docs)]
249pub enum Object {
250    Tree(Tree),
251    Blob(Blob),
252    Commit(Commit),
253    Tag(Tag),
254}
255/// A directory snapshot containing files (blobs), directories (trees) and submodules (commits).
256#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
258pub struct TreeRef<'a> {
259    /// The directories and files contained in this tree.
260    ///
261    /// Beware that the sort order isn't *quite* by name, so one may bisect only with a [`tree::EntryRef`] to handle ordering correctly.
262    #[cfg_attr(feature = "serde", serde(borrow))]
263    pub entries: Vec<tree::EntryRef<'a>>,
264}
265
266/// A directory snapshot containing files (blobs), directories (trees) and submodules (commits), lazily evaluated.
267#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
268pub struct TreeRefIter<'a> {
269    /// The hash kind to use for parsing this tree.
270    hash_kind: gix_hash::Kind,
271    /// The directories and files contained in this tree.
272    data: &'a [u8],
273}
274
275/// A mutable Tree, containing other trees, blobs or commits.
276#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
277#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
278pub struct Tree {
279    /// The directories and files contained in this tree. They must be and remain sorted by [`filename`][tree::Entry::filename].
280    ///
281    /// Beware that the sort order isn't *quite* by name, so one may bisect only with a [`tree::Entry`] to handle ordering correctly.
282    pub entries: Vec<tree::Entry>,
283}
284
285impl Tree {
286    /// Return an empty tree which serializes to a well-known hash
287    pub fn empty() -> Self {
288        Tree { entries: Vec::new() }
289    }
290}
291
292/// A borrowed object using a slice as backing buffer, or in other words a bytes buffer that knows the kind of object it represents.
293#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
294pub struct Data<'a> {
295    /// kind of object
296    pub kind: Kind,
297    /// The hash kind to use for parsing this data.
298    pub object_hash: gix_hash::Kind,
299    /// decoded, decompressed data, owned by a backing store.
300    pub data: &'a [u8],
301}
302
303/// Information about an object, which includes its kind and the amount of bytes it would have when obtained.
304#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
305pub struct Header {
306    /// The kind of object.
307    pub kind: Kind,
308    /// The object's size in bytes, or the size of the buffer when it's retrieved in full.
309    pub size: u64,
310}
311
312///
313pub mod decode {
314    use gix_error::Result;
315    mod error {
316        pub(crate) fn empty_error() -> gix_error::Message {
317            gix_error::validation("object parsing failed")
318        }
319    }
320
321    pub(crate) use error::empty_error;
322
323    use bstr::ByteSlice;
324    use gix_error::{OptionExt, ResultExt, validation};
325    /// Decode a loose object header, being `<kind> <size>\0`, returns
326    /// ([`kind`](super::Kind), `size`, `consumed bytes`).
327    ///
328    /// `size` is the uncompressed size of the payload in bytes.
329    /// Invalid kind or size fields include their bytes as `input` [metadata](gix_error::Error::metadata()).
330    pub fn loose_header(input: &[u8]) -> Result<(super::Kind, u64, usize)> {
331        let kind_end = input
332            .find_byte(0x20)
333            .ok_or_raise(|| validation("Expected '<type> <size>'"))?;
334        let kind = super::Kind::from_bytes(&input[..kind_end])
335            .or_raise(|| validation("The object header contained an unknown object kind."))?;
336        let size_end = input
337            .find_byte(0x0)
338            .ok_or_raise(|| validation("Did not find 0 byte in header"))?;
339        let size_bytes = &input[kind_end + 1..size_end];
340        let size = gix_utils::btoi::to_signed(size_bytes)
341            .or_raise(|| validation("Object size in header could not be parsed").with_input(size_bytes))?;
342        Ok((kind, size, size_end + 1))
343    }
344}
345
346fn object_hasher(hash_kind: gix_hash::Kind, object_kind: Kind, object_size: u64) -> gix_hash::Hasher {
347    let mut hasher = gix_hash::hasher(hash_kind);
348    hasher.update(&encode::loose_header(object_kind, object_size));
349    hasher
350}
351
352/// A function to compute a hash of kind `object_hash` for an object of `object_kind` and its `data`.
353#[doc(alias = "hash_object", alias = "git2")]
354pub fn compute_hash(hash_kind: gix_hash::Kind, object_kind: Kind, data: &[u8]) -> Result<gix_hash::ObjectId> {
355    let mut hasher = object_hasher(hash_kind, object_kind, data.len() as u64);
356    hasher.update(data);
357    hasher.try_finalize()
358}
359
360/// A function to compute a hash of kind `object_hash` for an object of `object_kind` and its data read from `stream`
361/// which has to yield exactly `stream_len` bytes.
362/// Use `progress` to learn about progress in bytes processed and `should_interrupt` to be able to abort the operation
363/// if set to `true`.
364#[doc(alias = "hash_file", alias = "git2")]
365pub fn compute_stream_hash(
366    hash_kind: gix_hash::Kind,
367    object_kind: Kind,
368    stream: &mut dyn std::io::Read,
369    stream_len: u64,
370    progress: &mut dyn gix_utils::progress::Progress,
371    should_interrupt: &std::sync::atomic::AtomicBool,
372) -> Result<gix_hash::ObjectId> {
373    let hasher = object_hasher(hash_kind, object_kind, stream_len);
374    gix_hash::bytes_with_hasher(stream, stream_len, hasher, progress, should_interrupt)
375}