Skip to main content

git_zero_server/receive/
report.rs

1use super::Capabilities;
2use git_zero_core::{Error, Result, io::Write, pktline, repository::validate_ref_name_bytes};
3
4/// A report-status/v2 encoder. Success is a host assertion: construct a report
5/// only after pack/graph validation and the authoritative publication outcome.
6/// Report v2 uses ordinary statuses when refs have not been rewritten by hooks.
7pub struct Report<'a, W: Write> {
8    output: &'a mut W,
9    encoder: Status,
10}
11
12/// Detached report encoder for paged ref outcomes in external storage. Every
13/// call accepts a new bounded sink. Discard all output after any sink failure.
14pub struct Status {
15    selected: Capabilities,
16    enabled: bool,
17    started: bool,
18    finished: bool,
19}
20
21impl Status {
22    pub fn new(selected: Capabilities) -> Self {
23        Self {
24            selected,
25            enabled: selected.contains(Capabilities::REPORT_STATUS)
26                || selected.contains(Capabilities::REPORT_STATUS_V2),
27            started: false,
28            finished: false,
29        }
30    }
31
32    pub fn unpack(
33        &mut self,
34        outcome: core::result::Result<(), &[u8]>,
35        output: &mut impl Write,
36    ) -> Result<()> {
37        if self.started || self.finished {
38            return Err(Error::InvalidRequest);
39        }
40        let message = outcome.err().unwrap_or(b"ok");
41        valid_message(message)?;
42        if outcome.is_err() && message == b"ok" {
43            return Err(Error::InvalidRequest);
44        }
45        self.started = true;
46        if self.enabled {
47            pktline::write_parts(
48                &mut Band {
49                    output,
50                    selected: self.selected,
51                },
52                &[b"unpack ", message, b"\n"],
53            )?;
54        }
55        Ok(())
56    }
57
58    pub fn reference(
59        &mut self,
60        name: &[u8],
61        outcome: core::result::Result<(), &[u8]>,
62        output: &mut impl Write,
63    ) -> Result<()> {
64        if !self.started || self.finished {
65            return Err(Error::InvalidRequest);
66        }
67        validate_ref_name_bytes(name)?;
68        if let Err(message) = outcome {
69            valid_message(message)?;
70        }
71        if !self.enabled {
72            return Ok(());
73        }
74        let mut band = Band {
75            output,
76            selected: self.selected,
77        };
78        match outcome {
79            Ok(()) => pktline::write_parts(&mut band, &[b"ok ", name, b"\n"]),
80            Err(message) => pktline::write_parts(&mut band, &[b"ng ", name, b" ", message, b"\n"]),
81        }
82    }
83
84    pub fn finish(&mut self, output: &mut impl Write) -> Result<()> {
85        if !self.started || self.finished {
86            return Err(Error::InvalidRequest);
87        }
88        self.finished = true;
89        if self.enabled {
90            Band {
91                output,
92                selected: self.selected,
93            }
94            .write_all(b"0000")?;
95        }
96        if self.selected.contains(Capabilities::SIDE_BAND_64K) {
97            output.write_all(b"0000")?;
98        }
99        Ok(())
100    }
101}
102
103impl<'a, W: Write> Report<'a, W> {
104    pub fn new(
105        output: &'a mut W,
106        selected: Capabilities,
107        unpack: core::result::Result<(), &[u8]>,
108    ) -> Result<Self> {
109        let mut encoder = Status::new(selected);
110        encoder.unpack(unpack, output)?;
111        Ok(Self { output, encoder })
112    }
113
114    pub fn reference(
115        &mut self,
116        name: &[u8],
117        outcome: core::result::Result<(), &[u8]>,
118    ) -> Result<()> {
119        self.encoder.reference(name, outcome, self.output)
120    }
121
122    pub fn finish(mut self) -> Result<()> {
123        self.encoder.finish(self.output)
124    }
125}
126
127struct Band<'a, W> {
128    output: &'a mut W,
129    selected: Capabilities,
130}
131impl<W: Write> Write for Report<'_, W> {
132    fn write_all(&mut self, bytes: &[u8]) -> Result<()> {
133        Band {
134            output: self.output,
135            selected: self.encoder.selected,
136        }
137        .write_all(bytes)
138    }
139}
140impl<W: Write> Write for Band<'_, W> {
141    fn write_all(&mut self, bytes: &[u8]) -> Result<()> {
142        if self.selected.contains(Capabilities::SIDE_BAND_64K) {
143            write_band(self.output, 1, bytes)
144        } else {
145            self.output.write_all(bytes)
146        }
147    }
148}
149
150/// Emit processing messages before the publication outcome is known, without
151/// sending an unpack/ref success status. Quiet and non-sideband clients get
152/// no bytes. The host starts `Report` only after validation/publication completes.
153pub fn write_progress(
154    output: &mut impl Write,
155    selected: Capabilities,
156    message: &[u8],
157) -> Result<()> {
158    if selected.contains(Capabilities::SIDE_BAND_64K) && !selected.contains(Capabilities::QUIET) {
159        write_band(output, 2, message)?;
160    }
161    Ok(())
162}
163
164fn write_band(output: &mut impl Write, channel: u8, bytes: &[u8]) -> Result<()> {
165    for chunk in bytes.chunks(pktline::MAX_LENGTH - 5) {
166        pktline::write_parts(output, &[&[channel], chunk])?;
167    }
168    Ok(())
169}
170
171fn valid_message(bytes: &[u8]) -> Result<()> {
172    if bytes.is_empty() || bytes.iter().any(|byte| matches!(byte, 0 | b'\r' | b'\n')) {
173        return Err(Error::InvalidRequest);
174    }
175    Ok(())
176}