girt/objects/header.rs
1//! Kind and size lookups that read object headers without decoding payloads.
2
3use std::sync::atomic::AtomicBool;
4
5use super::Objects;
6use crate::{ObjectId, ObjectKind, ObjectReadError, ReadLimits};
7
8/// Kind and payload size recorded in an object's stored header.
9///
10/// Header lookups do not inflate payloads or hash them, so this is a claim of the stored
11/// representation, not identity-verified data: use [`Objects::read`] for that.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct ObjectHeader {
14 kind: ObjectKind,
15 size: u64,
16}
17
18impl ObjectHeader {
19 /// Object kind; for a delta, the kind of its resolved base.
20 pub fn kind(&self) -> ObjectKind {
21 self.kind
22 }
23
24 /// Uncompressed payload size in bytes.
25 pub fn size(&self) -> u64 {
26 self.size
27 }
28}
29
30impl Objects {
31 /// Reads `id`'s kind and size from its stored header without decoding the payload.
32 ///
33 /// Uses the same store and pack precedence as [`Self::read`]. Packed deltas follow their
34 /// chain headers to the base kind; the size comes from the delta's own header. This is the
35 /// lookup behind `git cat-file --batch-check`. The empty tree is reported even when not
36 /// stored, as [`Self::read`] does.
37 ///
38 /// # Errors
39 ///
40 /// Returns corrupt or unsupported headers, I/O errors, missing delta bases, or
41 /// [`ObjectReadError::Limit`] when the size exceeds [`ReadLimits::max_object_bytes`] or the
42 /// chain exceeds [`ReadLimits::max_delta_depth`].
43 ///
44 /// ```
45 /// use girt::{InitKind, ObjectFormat, ObjectKind, PackLimits, ReadLimits, Repository};
46 ///
47 /// let directory = tempfile::tempdir()?;
48 /// let repo = Repository::init(
49 /// ObjectFormat::Sha1,
50 /// directory.path().join("r.git"),
51 /// InitKind::Bare,
52 /// )?;
53 ///
54 /// let id = repo.loose_objects().write_blob(b"seven\n")?;
55 /// let objects = repo.objects_with(PackLimits::default())?;
56 /// let header = objects
57 /// .header(id, ReadLimits::default())?
58 /// .ok_or("missing")?;
59 /// assert_eq!((header.kind(), header.size()), (ObjectKind::Blob, 6));
60 /// # Ok::<(), Box<dyn std::error::Error>>(())
61 /// ```
62 pub fn header(
63 &self,
64 id: ObjectId,
65 limits: ReadLimits,
66 ) -> Result<Option<ObjectHeader>, ObjectReadError> {
67 Ok(self
68 .locate(id, limits, &AtomicBool::new(false))?
69 .map(|(kind, size)| ObjectHeader { kind, size }))
70 }
71
72 /// Reads the headers of many objects, visiting packed entries in pack order.
73 ///
74 /// Equivalent to calling [`Self::header`] for each identity, but shared delta-chain headers
75 /// are read once per call and pack reads proceed in file order, which matters for listing a
76 /// whole repository. Results pair each identity with its header, or `None` when absent;
77 /// their order is unspecified. Use [`Self::headers_controlled`] to cancel.
78 ///
79 /// # Errors
80 ///
81 /// Returns the errors of [`Self::header`].
82 pub fn headers(
83 &self,
84 ids: impl IntoIterator<Item = ObjectId>,
85 limits: ReadLimits,
86 ) -> Result<Vec<(ObjectId, Option<ObjectHeader>)>, ObjectReadError> {
87 self.headers_controlled(ids, limits, &AtomicBool::new(false))
88 }
89
90 /// Reads the headers of many objects as [`Self::headers`] does, checking `cancelled` between
91 /// objects.
92 ///
93 /// # Errors
94 ///
95 /// Returns the errors of [`Self::header`] or [`ObjectReadError::Cancelled`].
96 pub fn headers_controlled(
97 &self,
98 ids: impl IntoIterator<Item = ObjectId>,
99 limits: ReadLimits,
100 cancelled: &AtomicBool,
101 ) -> Result<Vec<(ObjectId, Option<ObjectHeader>)>, ObjectReadError> {
102 Ok(self
103 .locate_all(ids, limits, cancelled)?
104 .into_iter()
105 .map(|(id, found)| (id, found.map(|(kind, size)| ObjectHeader { kind, size })))
106 .collect())
107 }
108}