Skip to main content

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}