1#![cfg_attr(
5 all(doc, feature = "document-features"),
6 doc = ::document_features::document_features!()
7)]
8#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))]
9#![deny(missing_docs, rust_2018_idioms)]
10#![forbid(unsafe_code)]
11
12use std::borrow::Cow;
13
14pub use bstr;
16use bstr::{BStr, BString, ByteSlice};
17pub use gix_date as date;
19use smallvec::SmallVec;
20
21pub mod commit;
23mod object;
24pub mod tag;
26pub mod tree;
28
29mod blob;
30pub mod data;
32
33pub mod find;
35
36pub mod write {
38 pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
40}
41
42mod traits;
43pub use traits::{Exists, Find, FindExt, FindObjectOrHeader, Header as FindHeader, HeaderExt, Write, WriteTo};
44
45pub mod encode;
46pub(crate) mod parse;
47
48pub mod kind;
50
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
54#[allow(missing_docs)]
55pub enum Kind {
56 Tree,
57 Blob,
58 Commit,
59 Tag,
60}
61#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub struct BlobRef<'a> {
65 pub data: &'a [u8],
67}
68
69#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct Blob {
73 pub data: Vec<u8>,
75}
76
77#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83pub struct CommitRef<'a> {
84 #[cfg_attr(feature = "serde", serde(borrow))]
88 pub tree: &'a BStr,
89 pub parents: SmallVec<[&'a BStr; 1]>,
91 #[cfg_attr(feature = "serde", serde(borrow))]
95 pub author: &'a BStr,
96 #[cfg_attr(feature = "serde", serde(borrow))]
100 pub committer: &'a BStr,
101 pub encoding: Option<&'a BStr>,
103 pub message: &'a BStr,
105 pub extra_headers: Vec<(&'a BStr, Cow<'a, BStr>)>,
107}
108
109#[derive(Copy, Clone)]
112pub struct CommitRefIter<'a> {
113 data: &'a [u8],
114 state: commit::ref_iter::State,
115}
116
117#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
119#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
120pub struct Commit {
121 pub tree: gix_hash::ObjectId,
123 pub parents: SmallVec<[gix_hash::ObjectId; 1]>,
125 pub author: gix_actor::Signature,
127 pub committer: gix_actor::Signature,
132 pub encoding: Option<BString>,
134 pub message: BString,
136 pub extra_headers: Vec<(BString, BString)>,
139}
140
141#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144pub struct TagRef<'a> {
145 #[cfg_attr(feature = "serde", serde(borrow))]
147 pub target: &'a BStr,
148 pub target_kind: Kind,
150 pub name: &'a BStr,
152 #[cfg_attr(feature = "serde", serde(borrow))]
156 pub tagger: Option<&'a BStr>,
157 pub message: &'a BStr,
159 pub pgp_signature: Option<&'a BStr>,
161}
162
163#[derive(Copy, Clone)]
166pub struct TagRefIter<'a> {
167 data: &'a [u8],
168 state: tag::ref_iter::State,
169}
170
171#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174pub struct Tag {
175 pub target: gix_hash::ObjectId,
177 pub target_kind: Kind,
179 pub name: BString,
181 pub tagger: Option<gix_actor::Signature>,
183 pub message: BString,
185 pub pgp_signature: Option<BString>,
187}
188
189#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
198#[allow(missing_docs)]
199pub enum ObjectRef<'a> {
200 #[cfg_attr(feature = "serde", serde(borrow))]
201 Tree(TreeRef<'a>),
202 Blob(BlobRef<'a>),
203 Commit(CommitRef<'a>),
204 Tag(TagRef<'a>),
205}
206
207#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
216#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
217#[allow(clippy::large_enum_variant, missing_docs)]
218pub enum Object {
219 Tree(Tree),
220 Blob(Blob),
221 Commit(Commit),
222 Tag(Tag),
223}
224#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
227pub struct TreeRef<'a> {
228 #[cfg_attr(feature = "serde", serde(borrow))]
232 pub entries: Vec<tree::EntryRef<'a>>,
233}
234
235#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
237pub struct TreeRefIter<'a> {
238 data: &'a [u8],
240}
241
242#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
244#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
245pub struct Tree {
246 pub entries: Vec<tree::Entry>,
250}
251
252impl Tree {
253 pub fn empty() -> Self {
255 Tree { entries: Vec::new() }
256 }
257}
258
259#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
261pub struct Data<'a> {
262 pub kind: Kind,
264 pub data: &'a [u8],
266}
267
268#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
270pub struct Header {
271 pub kind: Kind,
273 pub size: u64,
275}
276
277pub mod decode {
279 #[cfg(feature = "verbose-object-parsing-errors")]
280 mod _decode {
281 pub type ParseError = winnow::error::ContextError<winnow::error::StrContext>;
283
284 pub(crate) fn empty_error() -> Error {
285 Error {
286 inner: winnow::error::ContextError::new(),
287 remaining: Default::default(),
288 }
289 }
290
291 #[derive(Debug, Clone)]
293 pub struct Error {
294 pub inner: ParseError,
296 pub remaining: Vec<u8>,
298 }
299
300 impl Error {
301 pub(crate) fn with_err(err: winnow::error::ErrMode<ParseError>, remaining: &[u8]) -> Self {
302 Self {
303 inner: err.into_inner().expect("we don't have streaming parsers"),
304 remaining: remaining.to_owned(),
305 }
306 }
307 }
308
309 impl std::fmt::Display for Error {
310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 write!(f, "object parsing failed at `{}`", bstr::BStr::new(&self.remaining))?;
312 if self.inner.context().next().is_some() {
313 writeln!(f)?;
314 self.inner.fmt(f)?;
315 }
316 Ok(())
317 }
318 }
319
320 impl std::error::Error for Error {
321 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
322 self.inner.cause().map(|v| v as &(dyn std::error::Error + 'static))
323 }
324 }
325 }
326
327 #[cfg(not(feature = "verbose-object-parsing-errors"))]
329 mod _decode {
330 pub type ParseError = ();
332
333 pub(crate) fn empty_error() -> Error {
334 Error { inner: () }
335 }
336
337 #[derive(Debug, Clone)]
339 pub struct Error {
340 pub inner: ParseError,
342 }
343
344 impl Error {
345 pub(crate) fn with_err(err: winnow::error::ErrMode<ParseError>, _remaining: &[u8]) -> Self {
346 Self {
347 inner: err.into_inner().expect("we don't have streaming parsers"),
348 }
349 }
350 }
351
352 impl std::fmt::Display for Error {
353 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354 f.write_str("object parsing failed")
355 }
356 }
357
358 impl std::error::Error for Error {}
359 }
360 pub(crate) use _decode::empty_error;
361 pub use _decode::{Error, ParseError};
362
363 #[derive(Debug, thiserror::Error)]
365 #[allow(missing_docs)]
366 pub enum LooseHeaderDecodeError {
367 #[error("{message}: {number:?}")]
368 ParseIntegerError {
369 source: gix_utils::btoi::ParseIntegerError,
370 message: &'static str,
371 number: bstr::BString,
372 },
373 #[error("{message}")]
374 InvalidHeader { message: &'static str },
375 #[error("The object header contained an unknown object kind.")]
376 ObjectHeader(#[from] super::kind::Error),
377 }
378
379 use bstr::ByteSlice;
380 pub fn loose_header(input: &[u8]) -> Result<(super::Kind, u64, usize), LooseHeaderDecodeError> {
385 use LooseHeaderDecodeError::*;
386 let kind_end = input.find_byte(0x20).ok_or(InvalidHeader {
387 message: "Expected '<type> <size>'",
388 })?;
389 let kind = super::Kind::from_bytes(&input[..kind_end])?;
390 let size_end = input.find_byte(0x0).ok_or(InvalidHeader {
391 message: "Did not find 0 byte in header",
392 })?;
393 let size_bytes = &input[kind_end + 1..size_end];
394 let size = gix_utils::btoi::to_signed(size_bytes).map_err(|source| ParseIntegerError {
395 source,
396 message: "Object size in header could not be parsed",
397 number: size_bytes.into(),
398 })?;
399 Ok((kind, size, size_end + 1))
400 }
401}
402
403fn object_hasher(hash_kind: gix_hash::Kind, object_kind: Kind, object_size: u64) -> gix_hash::Hasher {
404 let mut hasher = gix_hash::hasher(hash_kind);
405 hasher.update(&encode::loose_header(object_kind, object_size));
406 hasher
407}
408
409#[doc(alias = "hash_object", alias = "git2")]
411pub fn compute_hash(
412 hash_kind: gix_hash::Kind,
413 object_kind: Kind,
414 data: &[u8],
415) -> Result<gix_hash::ObjectId, gix_hash::hasher::Error> {
416 let mut hasher = object_hasher(hash_kind, object_kind, data.len() as u64);
417 hasher.update(data);
418 hasher.try_finalize()
419}
420
421#[doc(alias = "hash_file", alias = "git2")]
426pub fn compute_stream_hash(
427 hash_kind: gix_hash::Kind,
428 object_kind: Kind,
429 stream: &mut dyn std::io::Read,
430 stream_len: u64,
431 progress: &mut dyn gix_features::progress::Progress,
432 should_interrupt: &std::sync::atomic::AtomicBool,
433) -> Result<gix_hash::ObjectId, gix_hash::io::Error> {
434 let hasher = object_hasher(hash_kind, object_kind, stream_len);
435 gix_hash::bytes_with_hasher(stream, stream_len, hasher, progress, should_interrupt)
436}