1use bytes::Bytes;
2use futures::stream::Stream;
3use std::fmt;
4use std::pin::Pin;
5use std::str::FromStr;
6
7pub type ProtocolStream = Pin<Box<dyn Stream<Item = Result<Bytes, ProtocolError>> + Send>>;
9
10#[derive(Debug, thiserror::Error)]
12pub enum ProtocolError {
13 #[error("Invalid service: {0}")]
14 InvalidService(String),
15
16 #[error("Repository not found: {0}")]
17 RepositoryNotFound(String),
18
19 #[error("Object not found: {0}")]
20 ObjectNotFound(String),
21
22 #[error("Invalid request: {0}")]
23 InvalidRequest(String),
24
25 #[error("Unauthorized: {0}")]
26 Unauthorized(String),
27
28 #[error("IO error: {0}")]
29 Io(#[from] std::io::Error),
30
31 #[error("Pack error: {0}")]
32 Pack(String),
33
34 #[error("Internal error: {0}")]
35 Internal(String),
36}
37
38impl ProtocolError {
39 pub fn invalid_service(service: &str) -> Self {
40 ProtocolError::InvalidService(service.to_string())
41 }
42
43 pub fn repository_error(msg: String) -> Self {
44 ProtocolError::Internal(msg)
45 }
46
47 pub fn invalid_request(msg: &str) -> Self {
48 ProtocolError::InvalidRequest(msg.to_string())
49 }
50
51 pub fn unauthorized(msg: &str) -> Self {
52 ProtocolError::Unauthorized(msg.to_string())
53 }
54}
55
56#[derive(Debug, PartialEq, Clone, Copy, Default)]
58pub enum TransportProtocol {
59 Local,
60 #[default]
61 Http,
62 Ssh,
63 Git,
64}
65
66#[derive(Debug, PartialEq, Clone, Copy)]
68pub enum ServiceType {
69 UploadPack,
70 ReceivePack,
71}
72
73impl fmt::Display for ServiceType {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 match self {
76 ServiceType::UploadPack => write!(f, "git-upload-pack"),
77 ServiceType::ReceivePack => write!(f, "git-receive-pack"),
78 }
79 }
80}
81
82impl FromStr for ServiceType {
83 type Err = ProtocolError;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 match s {
87 "git-upload-pack" => Ok(ServiceType::UploadPack),
88 "git-receive-pack" => Ok(ServiceType::ReceivePack),
89 _ => Err(ProtocolError::InvalidService(s.to_string())),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq)]
116pub enum Capability {
117 MultiAck,
119 MultiAckDetailed,
121 NoDone,
123 SideBand,
125 SideBand64k,
127 ReportStatus,
129 ReportStatusv2,
131 OfsDelta,
133 DeepenSince,
135 DeepenNot,
137 DeepenRelative,
139 ThinPack,
141 Shallow,
143 IncludeTag,
145 DeleteRefs,
147 Quiet,
149 Atomic,
151 NoThin,
153 NoProgress,
155 AllowTipSha1InWant,
157 AllowReachableSha1InWant,
159 PushCert(String),
161 PushOptions,
163 ObjectFormat(String),
165 SessionId(String),
167 Filter(String),
169 Symref(String),
171 Agent(String),
173 Unknown(String),
175}
176
177impl FromStr for Capability {
178 type Err = ();
179
180 fn from_str(s: &str) -> Result<Self, Self::Err> {
181 if let Some(rest) = s.strip_prefix("agent=") {
183 return Ok(Capability::Agent(rest.to_string()));
184 }
185 if let Some(rest) = s.strip_prefix("session-id=") {
186 return Ok(Capability::SessionId(rest.to_string()));
187 }
188 if let Some(rest) = s.strip_prefix("push-cert=") {
189 return Ok(Capability::PushCert(rest.to_string()));
190 }
191 if let Some(rest) = s.strip_prefix("object-format=") {
192 return Ok(Capability::ObjectFormat(rest.to_string()));
193 }
194 if let Some(rest) = s.strip_prefix("filter=") {
195 return Ok(Capability::Filter(rest.to_string()));
196 }
197 if let Some(rest) = s.strip_prefix("symref=") {
198 return Ok(Capability::Symref(rest.to_string()));
199 }
200
201 match s {
202 "multi_ack" => Ok(Capability::MultiAck),
203 "multi_ack_detailed" => Ok(Capability::MultiAckDetailed),
204 "no-done" => Ok(Capability::NoDone),
205 "side-band" => Ok(Capability::SideBand),
206 "side-band-64k" => Ok(Capability::SideBand64k),
207 "report-status" => Ok(Capability::ReportStatus),
208 "report-status-v2" => Ok(Capability::ReportStatusv2),
209 "ofs-delta" => Ok(Capability::OfsDelta),
210 "deepen-since" => Ok(Capability::DeepenSince),
211 "deepen-not" => Ok(Capability::DeepenNot),
212 "deepen-relative" => Ok(Capability::DeepenRelative),
213 "thin-pack" => Ok(Capability::ThinPack),
214 "shallow" => Ok(Capability::Shallow),
215 "include-tag" => Ok(Capability::IncludeTag),
216 "delete-refs" => Ok(Capability::DeleteRefs),
217 "quiet" => Ok(Capability::Quiet),
218 "atomic" => Ok(Capability::Atomic),
219 "no-thin" => Ok(Capability::NoThin),
220 "no-progress" => Ok(Capability::NoProgress),
221 "allow-tip-sha1-in-want" => Ok(Capability::AllowTipSha1InWant),
222 "allow-reachable-sha1-in-want" => Ok(Capability::AllowReachableSha1InWant),
223 "push-options" => Ok(Capability::PushOptions),
224 _ => Ok(Capability::Unknown(s.to_string())),
225 }
226 }
227}
228
229impl std::fmt::Display for Capability {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 match self {
232 Capability::MultiAck => write!(f, "multi_ack"),
233 Capability::MultiAckDetailed => write!(f, "multi_ack_detailed"),
234 Capability::NoDone => write!(f, "no-done"),
235 Capability::SideBand => write!(f, "side-band"),
236 Capability::SideBand64k => write!(f, "side-band-64k"),
237 Capability::ReportStatus => write!(f, "report-status"),
238 Capability::ReportStatusv2 => write!(f, "report-status-v2"),
239 Capability::OfsDelta => write!(f, "ofs-delta"),
240 Capability::DeepenSince => write!(f, "deepen-since"),
241 Capability::DeepenNot => write!(f, "deepen-not"),
242 Capability::DeepenRelative => write!(f, "deepen-relative"),
243 Capability::ThinPack => write!(f, "thin-pack"),
244 Capability::Shallow => write!(f, "shallow"),
245 Capability::IncludeTag => write!(f, "include-tag"),
246 Capability::DeleteRefs => write!(f, "delete-refs"),
247 Capability::Quiet => write!(f, "quiet"),
248 Capability::Atomic => write!(f, "atomic"),
249 Capability::NoThin => write!(f, "no-thin"),
250 Capability::NoProgress => write!(f, "no-progress"),
251 Capability::AllowTipSha1InWant => write!(f, "allow-tip-sha1-in-want"),
252 Capability::AllowReachableSha1InWant => write!(f, "allow-reachable-sha1-in-want"),
253 Capability::PushCert(value) => write!(f, "push-cert={}", value),
254 Capability::PushOptions => write!(f, "push-options"),
255 Capability::ObjectFormat(format) => write!(f, "object-format={}", format),
256 Capability::SessionId(id) => write!(f, "session-id={}", id),
257 Capability::Filter(filter) => write!(f, "filter={}", filter),
258 Capability::Symref(symref) => write!(f, "symref={}", symref),
259 Capability::Agent(agent) => write!(f, "agent={}", agent),
260 Capability::Unknown(s) => write!(f, "{}", s),
261 }
262 }
263}
264
265pub enum SideBand {
267 PackfileData,
269 ProgressInfo,
271 Error,
273}
274
275impl SideBand {
276 pub fn value(&self) -> u8 {
277 match self {
278 Self::PackfileData => b'\x01',
279 Self::ProgressInfo => b'\x02',
280 Self::Error => b'\x03',
281 }
282 }
283}
284
285#[derive(Debug, PartialEq, Clone, Copy)]
287pub enum RefTypeEnum {
288 Branch,
289 Tag,
290}
291
292#[derive(Clone, Debug)]
294pub struct GitRef {
295 pub name: String,
296 pub hash: String,
297}
298
299#[derive(Debug, Clone)]
301pub struct RefCommand {
302 pub old_hash: String,
303 pub new_hash: String,
304 pub ref_name: String,
305 pub ref_type: RefTypeEnum,
306 pub default_branch: bool,
307 pub status: CommandStatus,
308 pub error_message: Option<String>,
309}
310
311#[derive(Debug, Clone)]
312pub enum CommandStatus {
313 Pending,
314 Success,
315 Failed,
316}
317
318impl RefCommand {
319 pub fn new(old_hash: String, new_hash: String, ref_name: String) -> Self {
320 let ref_type = if ref_name.starts_with("refs/tags/") {
322 RefTypeEnum::Tag
323 } else {
324 RefTypeEnum::Branch
325 };
326
327 Self {
328 old_hash,
329 new_hash,
330 ref_name,
331 ref_type,
332 default_branch: false,
333 status: CommandStatus::Pending,
334 error_message: None,
335 }
336 }
337
338 pub fn failed(&mut self, error: String) {
339 self.status = CommandStatus::Failed;
340 self.error_message = Some(error);
341 }
342
343 pub fn success(&mut self) {
344 self.status = CommandStatus::Success;
345 self.error_message = None;
346 }
347
348 pub fn get_status(&self) -> String {
349 match &self.status {
350 CommandStatus::Success => format!("ok {}", self.ref_name),
351 CommandStatus::Failed => {
352 let error = self.error_message.as_deref().unwrap_or("unknown error");
353 format!("ng {} {}", self.ref_name, error)
354 }
355 CommandStatus::Pending => format!("ok {}", self.ref_name), }
357 }
358}
359
360#[derive(Debug, PartialEq, Clone)]
361pub enum CommandType {
362 Create,
363 Update,
364 Delete,
365}
366
367pub const ZERO_ID: &str = "0000000000000000000000000000000000000000";
369
370pub const LF: char = '\n';
372pub const SP: char = ' ';
373pub const NUL: char = '\0';
374pub const PKT_LINE_END_MARKER: &[u8; 4] = b"0000";
375
376pub const RECEIVE_CAP_LIST: &str =
378 "report-status report-status-v2 delete-refs quiet atomic no-thin ";
379pub const COMMON_CAP_LIST: &str = "side-band-64k ofs-delta agent=git-internal/0.1.0";
380pub const UPLOAD_CAP_LIST: &str = "multi_ack_detailed no-done include-tag ";