git_zero_server/receive/
report.rs1use super::Capabilities;
2use git_zero_core::{Error, Result, io::Write, pktline, repository::validate_ref_name_bytes};
3
4pub struct Report<'a, W: Write> {
8 output: &'a mut W,
9 encoder: Status,
10}
11
12pub 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
150pub 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}