Skip to main content

git_zero_server/stream/
preparation.rs

1//! A transport-silent handoff between selection and asynchronous pack storage.
2//!
3//! The host owns this value and its caller buffers across awaits. Neither the
4//! request body nor the temporary validation index is borrowed after preparation.
5//! Keep the authorized repository and selected/control buffers immutable until
6//! attachment or cancellation. Independent requests require independent storage.
7use super::{Body, ResponseEncoder};
8use git_zero_core::{
9    Error, ObjectId, Repository, Result,
10    cache::selected::{Key, Selection},
11    pack,
12};
13
14/// A prepared response whose raw pack has not yet been exposed to transport.
15/// Only raw PACK bytes may be persisted; request-specific ACKs, boundaries and
16/// sideband framing stay in the retained response encoder.
17pub struct PreparedResponse<'a> {
18    encoder: ResponseEncoder<'a>,
19    pack: Option<pack::Encoder<'a>>,
20    key: Option<Key>,
21}
22
23impl<'a> PreparedResponse<'a> {
24    /// Consume a freshly prepared, unread response. Authorization, selection,
25    /// graph limits and control-buffer admission must already have succeeded.
26    /// A response without a pack (including negotiation/probes) has no cache key.
27    pub fn new(
28        mut encoder: ResponseEncoder<'a>,
29        repository: &impl Repository,
30        ids: &mut [ObjectId],
31    ) -> Result<Self> {
32        if encoder.started || encoder.cancelled {
33            return Err(Error::InvalidRequest);
34        }
35        let (pack, key) = match core::mem::replace(&mut encoder.body, Body::Buffered) {
36            Body::Pack(pack) => {
37                let selection = pack.selection().map_or(Selection::All, Selection::Indices);
38                let key = Key::derive(repository, selection, ids)?;
39                (Some(pack), Some(key))
40            }
41            body => {
42                encoder.body = body;
43                (None, None)
44            }
45        };
46        Ok(Self { encoder, pack, key })
47    }
48
49    pub fn key(&self) -> Option<Key> {
50        self.key
51    }
52
53    pub fn response(&self) -> crate::Response {
54        self.encoder.response()
55    }
56
57    /// Generate raw bytes on a miss without exposing any HTTP/protocol output.
58    /// Pulls remain bounded by `output`; zero denotes complete PACK EOF. The
59    /// immutable repository must be the one used to prepare this response.
60    /// Discard staging on error. A fresh preparation is needed to restart it.
61    pub fn read_pack(&mut self, repository: &impl Repository, output: &mut [u8]) -> Result<usize> {
62        self.pack
63            .as_mut()
64            .ok_or(Error::InvalidRequest)?
65            .read(repository, output)
66    }
67
68    /// Verify framing, checksums, canonical IDs and the exact selected set before
69    /// returning an ordinary response cursor. Works for hits and generated misses.
70    /// Cache bytes are borrowed and must remain immutable through transport EOF.
71    /// Publication must be awaited separately before exposing the returned cursor.
72    pub fn attach(
73        mut self,
74        bytes: &'a [u8],
75        ids: &mut [ObjectId],
76        byte_limit: usize,
77    ) -> Result<ResponseEncoder<'a>> {
78        self.key
79            .ok_or(Error::InvalidRequest)?
80            .verify_pack(bytes, ids, byte_limit)?;
81        self.encoder.body = Body::Cached { bytes, position: 0 };
82        Ok(self.encoder)
83    }
84
85    /// Finish a non-pack response without consulting storage. Pack responses
86    /// require authenticated attachment, even after complete miss generation.
87    pub fn without_pack(self) -> Result<ResponseEncoder<'a>> {
88        if self.key.is_some() {
89            return Err(Error::InvalidRequest);
90        }
91        Ok(self.encoder)
92    }
93}