Skip to main content

git_zero_core/pack/
encode.rs

1//! Streaming pack and loose-object encoding.
2use crate::{
3    Error, Object, ObjectId, ObjectInfo, ObjectReader, Repository, Result, io::Write,
4    reader::DEFAULT_READ_BUFFER_SIZE, zlib,
5};
6
7/// Generate a complete version-2 pack with no deltas, without buffering the pack.
8/// Reserves 1 KiB of read scratch on the stack for streaming-only providers.
9pub fn write(repository: &impl Repository, output: &mut impl Write) -> Result<ObjectId> {
10    write_with_scratch(repository, &mut [0; DEFAULT_READ_BUFFER_SIZE], output)
11}
12
13/// Stream borrowed, file-backed, or generated objects using caller-owned scratch.
14/// A nonempty buffer suffices for any body size; borrowed bodies need no scratch.
15/// Output is independent of the buffer size, preserving pack/cache identities.
16/// Errors invalidate partial output. Scratch is reused for each object; encoder
17/// and hash state are inline and independent of object/pack sizes.
18pub fn write_with_scratch(
19    repository: &impl Repository,
20    scratch: &mut [u8],
21    output: &mut impl Write,
22) -> Result<ObjectId> {
23    write_encoder(repository, None, scratch, output)
24}
25
26/// Write exactly the chosen object indices, in caller-specified order. Indices
27/// must be valid and unique; violations fail before any output. Use a validated
28/// repository's `select_objects` result for an exact reachable closure. Encoder
29/// and read budgets match `write_with_scratch`; no pack-sized buffer is needed.
30pub fn write_selected_with_scratch(
31    repository: &impl Repository,
32    indices: &[usize],
33    scratch: &mut [u8],
34    output: &mut impl Write,
35) -> Result<ObjectId> {
36    write_encoder(repository, Some(indices), scratch, output)
37}
38
39pub(super) fn validate_order(repository: &impl Repository, indices: &[usize]) -> Result<()> {
40    let ordered = indices.windows(2).all(|pair| pair[0] < pair[1]);
41    for (position, &index) in indices.iter().enumerate() {
42        if index >= repository.object_count() {
43            return Err(Error::ObjectNotFound);
44        }
45        if !ordered && indices[..position].contains(&index) {
46            return Err(Error::InvalidObject);
47        }
48    }
49    Ok(())
50}
51
52fn write_encoder(
53    repository: &impl Repository,
54    indices: Option<&[usize]>,
55    scratch: &mut [u8],
56    output: &mut impl Write,
57) -> Result<ObjectId> {
58    let mut encoder = super::Encoder::new(repository, indices)?;
59    if scratch.is_empty() {
60        // Preserve the historical scratchless borrowed-object API. Generated
61        // nonempty bodies still require caller-provided read storage.
62        let count = indices.map_or(repository.object_count(), <[usize]>::len);
63        for position in 0..count {
64            let index = indices.map_or(position, |indices| indices[position]);
65            if repository.object(index).is_none() && repository.object_info(index)?.size != 0 {
66                return Err(Error::BufferTooSmall);
67            }
68        }
69        return drain_encoder(
70            repository,
71            &mut encoder,
72            &mut [0; DEFAULT_READ_BUFFER_SIZE],
73            output,
74        );
75    }
76    drain_encoder(repository, &mut encoder, scratch, output)
77}
78
79fn drain_encoder(
80    repository: &impl Repository,
81    encoder: &mut super::Encoder<'_>,
82    scratch: &mut [u8],
83    output: &mut impl Write,
84) -> Result<ObjectId> {
85    loop {
86        let count = encoder.read(repository, scratch)?;
87        if count == 0 {
88            return encoder.checksum().ok_or(Error::InvalidObject);
89        }
90        output.write_all(&scratch[..count])?;
91    }
92}
93
94pub(super) fn write_entry_header(info: ObjectInfo, output: &mut impl Write) -> Result<()> {
95    write_typed_header(info.kind as u8, info.size, output)
96}
97
98pub(super) fn write_typed_header(kind: u8, mut size: u64, output: &mut impl Write) -> Result<()> {
99    let mut byte = (kind << 4) | (size as u8 & 15);
100    size >>= 4;
101    loop {
102        output.write_all(&[byte | if size > 0 { 128 } else { 0 }])?;
103        if size == 0 {
104            return Ok(());
105        }
106        byte = size as u8 & 127;
107        size >>= 7;
108    }
109}
110
111pub fn write_loose(object: Object<'_>, output: &mut impl Write) -> Result<()> {
112    let mut storage = [0; 32];
113    zlib::write_parts(output, &[object.header(&mut storage)?, object.data])
114}
115
116/// Stream a loose object by index. Borrowed bodies use `write_loose` directly;
117/// other providers need nonempty scratch for nonempty bodies. Output is identical
118/// for every scratch size. Discard partial output on error.
119pub fn write_loose_with_scratch(
120    repository: &impl Repository,
121    index: usize,
122    scratch: &mut [u8],
123    output: &mut impl Write,
124) -> Result<()> {
125    if let Some(object) = repository.object(index) {
126        return write_loose(object, output);
127    }
128    let mut reader = ObjectReader::new(repository, index)?;
129    let mut header = [0; 32];
130    let header = reader.info().header(&mut header)?;
131    zlib::write_reader(output, header, &mut reader, scratch)
132}