1use super::Capabilities;
2use git_zero_core::{
3 Error, ObjectFormat, ObjectId, Result,
4 io::{SliceWriter, Write},
5 pktline,
6 repository::validate_ref_name_bytes,
7};
8
9pub struct Advertisement<'a> {
12 format: ObjectFormat,
13 capabilities: Capabilities,
14 previous: &'a mut [u8],
15 length: usize,
16 started: bool,
17 finished: bool,
18}
19
20impl<'a> Advertisement<'a> {
21 pub fn new(format: ObjectFormat, capabilities: Capabilities, previous: &'a mut [u8]) -> Self {
22 Self {
23 format,
24 capabilities,
25 previous,
26 length: 0,
27 started: false,
28 finished: false,
29 }
30 }
31
32 pub fn start(&mut self, output: &mut impl Write) -> Result<()> {
33 if self.started || self.finished {
34 return Err(Error::InvalidRequest);
35 }
36 self.started = true;
37 pktline::write(output, b"# service=git-receive-pack\n")?;
38 output.write_all(b"0000")
39 }
40
41 pub fn reference(&mut self, id: ObjectId, name: &[u8], output: &mut impl Write) -> Result<()> {
42 if !self.started || self.finished {
43 return Err(Error::InvalidRequest);
44 }
45 if name.len() > self.previous.len() {
46 return Err(Error::BufferTooSmall);
47 }
48 let previous = (self.length != 0).then_some(&self.previous[..self.length]);
49 reference(self.format, self.capabilities, id, name, previous, output)?;
50 self.previous[..name.len()].copy_from_slice(name);
51 self.length = name.len();
52 Ok(())
53 }
54
55 pub fn finish(&mut self, output: &mut impl Write) -> Result<()> {
56 if !self.started || self.finished {
57 return Err(Error::InvalidRequest);
58 }
59 self.finished = true;
60 finish(self.format, self.capabilities, self.length == 0, output)
61 }
62}
63
64pub fn write_advertisement<'a>(
68 format: ObjectFormat,
69 capabilities: Capabilities,
70 refs: impl IntoIterator<Item = (ObjectId, &'a [u8])>,
71 output: &mut impl Write,
72) -> Result<()> {
73 pktline::write(output, b"# service=git-receive-pack\n")?;
74 output.write_all(b"0000")?;
75 let mut previous: Option<&[u8]> = None;
76 for (id, name) in refs {
77 reference(format, capabilities, id, name, previous, output)?;
78 previous = Some(name);
79 }
80 finish(format, capabilities, previous.is_none(), output)
81}
82
83fn reference(
84 format: ObjectFormat,
85 capabilities: Capabilities,
86 id: ObjectId,
87 name: &[u8],
88 previous: Option<&[u8]>,
89 output: &mut impl Write,
90) -> Result<()> {
91 validate_ref_name_bytes(name)?;
92 if id.format() != format || id.is_zero() || previous.is_some_and(|previous| previous >= name) {
93 return Err(Error::InvalidReference);
94 }
95 if previous.is_none() {
96 first(format, capabilities, id, name, output)
97 } else {
98 pktline::write_parts(output, &[id.to_hex().as_bytes(), b" ", name, b"\n"])
99 }
100}
101
102fn first(
103 format: ObjectFormat,
104 capabilities: Capabilities,
105 id: ObjectId,
106 name: &[u8],
107 output: &mut impl Write,
108) -> Result<()> {
109 let mut storage = [0; 256];
110 let mut caps = SliceWriter::new(&mut storage);
111 capabilities.write(format, &mut caps)?;
112 pktline::write_parts(
113 output,
114 &[
115 id.to_hex().as_bytes(),
116 b" ",
117 name,
118 b"\0",
119 caps.finish(),
120 b"\n",
121 ],
122 )
123}
124
125pub(super) fn validate_first_name(
126 format: ObjectFormat,
127 capabilities: Capabilities,
128 name: &[u8],
129) -> Result<()> {
130 struct Discard;
133 impl Write for Discard {
134 fn write_all(&mut self, _: &[u8]) -> Result<()> {
135 Ok(())
136 }
137 }
138 first(format, capabilities, format.zero_id(), name, &mut Discard).map_err(|error| {
139 if error == Error::InvalidPacket {
140 Error::InvalidReference
141 } else {
142 error
143 }
144 })
145}
146
147fn finish(
148 format: ObjectFormat,
149 capabilities: Capabilities,
150 empty: bool,
151 output: &mut impl Write,
152) -> Result<()> {
153 if empty {
154 first(
155 format,
156 capabilities,
157 format.zero_id(),
158 b"capabilities^{}",
159 output,
160 )?;
161 }
162 output.write_all(b"0000")
163}