1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
4
5use sley_core::{Capability, GitError, ObjectFormat, ObjectId, Result};
6use std::io::{ErrorKind, Read, Write};
7
8pub const PKT_LINE_MAX_LEN: usize = 65_520;
9
10pub const PKT_LINE_MAX_PAYLOAD_LEN: usize = PKT_LINE_MAX_LEN - 4;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ProtocolVersion {
14 V0,
15 V1,
16 V2,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct PktLine(pub Vec<u8>);
21
22impl PktLine {
23 pub fn encode(&self) -> Vec<u8> {
24 encode_pkt_line_payload(&self.0)
25 }
26
27 pub fn try_encode(&self) -> Result<Vec<u8>> {
28 validate_pkt_line_payload(&self.0)?;
29 Ok(self.encode())
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum PktLineFrame {
35 Data(Vec<u8>),
36 Flush,
37 Delimiter,
38 ResponseEnd,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct ProtocolErrorLine {
43 pub message: String,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum GitService {
48 UploadPack,
49 ReceivePack,
50 UploadArchive,
51}
52
53impl GitService {
54 pub fn as_str(self) -> &'static str {
55 match self {
56 Self::UploadPack => "git-upload-pack",
57 Self::ReceivePack => "git-receive-pack",
58 Self::UploadArchive => "git-upload-archive",
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct RefSpec {
65 pub force: bool,
66 pub negative: bool,
67 pub src: Option<String>,
68 pub dst: Option<String>,
69 pub pattern: bool,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct FetchHeadRecord {
74 pub oid: ObjectId,
75 pub not_for_merge: bool,
76 pub description: String,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct FetchRefUpdate {
81 pub src: String,
82 pub dst: Option<String>,
83 pub oid: ObjectId,
84 pub not_for_merge: bool,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct PushSourceRef {
89 pub name: String,
90 pub oid: ObjectId,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum SideBandChannel {
95 Data,
96 Progress,
97 Fatal,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct SideBandPacket {
102 pub channel: SideBandChannel,
103 pub data: Vec<u8>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Default)]
107pub struct SideBandDemux {
108 pub data: Vec<u8>,
109 pub progress: Vec<Vec<u8>>,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Default)]
113pub struct UploadArchiveRequest {
114 pub arguments: Vec<String>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum UploadArchiveResponse {
119 Ack { sideband: Vec<SideBandPacket> },
120 Nack { message: String },
121}
122
123impl PktLineFrame {
124 pub fn data(payload: impl Into<Vec<u8>>) -> Result<Self> {
125 let payload = payload.into();
126 validate_pkt_line_payload(&payload)?;
127 Ok(Self::Data(payload))
128 }
129
130 pub fn encode(&self) -> Vec<u8> {
131 match self {
132 Self::Data(payload) => encode_pkt_line_payload(payload),
133 Self::Flush => b"0000".to_vec(),
134 Self::Delimiter => b"0001".to_vec(),
135 Self::ResponseEnd => b"0002".to_vec(),
136 }
137 }
138
139 pub fn try_encode(&self) -> Result<Vec<u8>> {
140 match self {
141 Self::Data(payload) => try_encode_pkt_line_payload(payload),
142 Self::Flush | Self::Delimiter | Self::ResponseEnd => Ok(self.encode()),
143 }
144 }
145
146 pub fn parse(input: &[u8]) -> Result<(Self, usize)> {
147 if input.len() < 4 {
148 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
149 }
150 let len = parse_pkt_len(&input[..4])?;
151 match len {
152 0 => Ok((Self::Flush, 4)),
153 1 => Ok((Self::Delimiter, 4)),
154 2 => Ok((Self::ResponseEnd, 4)),
155 3 => Err(GitError::InvalidFormat(
156 "reserved pkt-line length 0003".into(),
157 )),
158 4..=PKT_LINE_MAX_LEN => {
159 if input.len() < len {
160 return Err(GitError::InvalidFormat(format!(
161 "truncated pkt-line payload: expected {} bytes, got {}",
162 len - 4,
163 input.len().saturating_sub(4)
164 )));
165 }
166 Ok((Self::Data(input[4..len].to_vec()), len))
167 }
168 _ => Err(GitError::InvalidFormat(format!(
169 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
170 ))),
171 }
172 }
173}
174
175fn validate_pkt_line_payload(payload: &[u8]) -> Result<()> {
176 if payload.len() > PKT_LINE_MAX_PAYLOAD_LEN {
177 return Err(GitError::InvalidFormat(format!(
178 "pkt-line payload exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
179 )));
180 }
181 Ok(())
182}
183
184fn pkt_line_header(len: usize) -> [u8; 4] {
185 const HEX: &[u8; 16] = b"0123456789abcdef";
186 [
187 HEX[(len >> 12) & 0xf],
188 HEX[(len >> 8) & 0xf],
189 HEX[(len >> 4) & 0xf],
190 HEX[len & 0xf],
191 ]
192}
193
194fn encode_pkt_line_payload(payload: &[u8]) -> Vec<u8> {
195 let len = payload.len() + 4;
196 let mut out = Vec::with_capacity(len);
197 out.extend_from_slice(&pkt_line_header(len));
198 out.extend_from_slice(payload);
199 out
200}
201
202fn try_encode_pkt_line_payload(payload: &[u8]) -> Result<Vec<u8>> {
203 validate_pkt_line_payload(payload)?;
204 Ok(encode_pkt_line_payload(payload))
205}
206
207pub fn parse_pkt_line_stream(mut input: &[u8]) -> Result<Vec<PktLineFrame>> {
208 let mut frames = Vec::new();
209 while !input.is_empty() {
210 let (frame, consumed) = PktLineFrame::parse(input)?;
211 frames.push(frame);
212 input = &input[consumed..];
213 }
214 Ok(frames)
215}
216
217fn parse_pkt_line_frames_until_flush_from(mut input: &[u8]) -> Result<(Vec<PktLineFrame>, usize)> {
218 let mut frames = Vec::new();
219 let mut total = 0usize;
220 loop {
221 if input.is_empty() {
222 return Err(GitError::InvalidFormat(
223 "pkt-line stream ended before flush".into(),
224 ));
225 }
226 let (frame, consumed) = PktLineFrame::parse(input)?;
227 total += consumed;
228 let done = matches!(frame, PktLineFrame::Flush);
229 frames.push(frame);
230 input = &input[consumed..];
231 if done {
232 return Ok((frames, total));
233 }
234 }
235}
236
237pub fn read_pkt_line_frame(reader: &mut impl Read) -> Result<Option<PktLineFrame>> {
238 let mut header = [0u8; 4];
239 let mut read = 0usize;
240 while read < header.len() {
241 match reader.read(&mut header[read..]) {
242 Ok(0) if read == 0 => return Ok(None),
243 Ok(0) => {
244 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
245 }
246 Ok(n) => read += n,
247 Err(err) if err.kind() == ErrorKind::Interrupted => {}
248 Err(err) => return Err(err.into()),
249 }
250 }
251
252 let len = parse_pkt_len(&header)?;
253 match len {
254 0 => Ok(Some(PktLineFrame::Flush)),
255 1 => Ok(Some(PktLineFrame::Delimiter)),
256 2 => Ok(Some(PktLineFrame::ResponseEnd)),
257 3 => Err(GitError::InvalidFormat(
258 "reserved pkt-line length 0003".into(),
259 )),
260 4..=PKT_LINE_MAX_LEN => {
261 let mut payload = vec![0; len - 4];
262 reader.read_exact(&mut payload)?;
263 Ok(Some(PktLineFrame::Data(payload)))
264 }
265 _ => Err(GitError::InvalidFormat(format!(
266 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
267 ))),
268 }
269}
270
271pub fn read_pkt_line_frames(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
272 let mut frames = Vec::new();
273 while let Some(frame) = read_pkt_line_frame(reader)? {
274 frames.push(frame);
275 }
276 Ok(frames)
277}
278
279pub fn read_pkt_line_frames_until_flush(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
280 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::Flush))
281}
282
283pub fn read_pkt_line_frames_until_response_end(
284 reader: &mut impl Read,
285) -> Result<Vec<PktLineFrame>> {
286 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::ResponseEnd))
287}
288
289fn read_pkt_line_frames_until_control(
290 reader: &mut impl Read,
291 stop: impl Fn(&PktLineFrame) -> bool,
292) -> Result<Vec<PktLineFrame>> {
293 let mut frames = Vec::new();
294 loop {
295 let Some(frame) = read_pkt_line_frame(reader)? else {
296 return Err(GitError::InvalidFormat(
297 "pkt-line stream ended before control packet".into(),
298 ));
299 };
300 let done = stop(&frame);
301 frames.push(frame);
302 if done {
303 return Ok(frames);
304 }
305 }
306}
307
308pub fn write_pkt_line_frame(writer: &mut impl Write, frame: &PktLineFrame) -> Result<()> {
309 match frame {
310 PktLineFrame::Data(payload) => write_pkt_line_payload(writer, payload)?,
311 PktLineFrame::Flush => writer.write_all(b"0000")?,
312 PktLineFrame::Delimiter => writer.write_all(b"0001")?,
313 PktLineFrame::ResponseEnd => writer.write_all(b"0002")?,
314 }
315 Ok(())
316}
317
318pub fn write_pkt_line_payload(writer: &mut impl Write, payload: &[u8]) -> Result<()> {
319 validate_pkt_line_payload(payload)?;
320 let len = payload.len() + 4;
321 writer.write_all(&pkt_line_header(len))?;
322 writer.write_all(payload)?;
323 Ok(())
324}
325
326pub fn write_pkt_line_frames(writer: &mut impl Write, frames: &[PktLineFrame]) -> Result<()> {
327 for frame in frames {
328 write_pkt_line_frame(writer, frame)?;
329 }
330 Ok(())
331}
332
333pub fn parse_error_line(payload: &[u8]) -> Result<ProtocolErrorLine> {
334 let text = parse_protocol_v2_line_text("protocol error line", payload)?;
335 let Some(message) = text.strip_prefix("ERR ") else {
336 return Err(GitError::InvalidFormat(
337 "protocol error line must start with ERR".into(),
338 ));
339 };
340 validate_protocol_error_message(message)?;
341 Ok(ProtocolErrorLine {
342 message: message.to_string(),
343 })
344}
345
346pub fn encode_error_line(error: &ProtocolErrorLine) -> Result<Vec<u8>> {
347 validate_protocol_error_message(&error.message)?;
348 Ok(line_from_str(&format!("ERR {}", error.message)))
349}
350
351pub fn parse_error_frame(frame: &PktLineFrame) -> Result<Option<ProtocolErrorLine>> {
352 match frame {
353 PktLineFrame::Data(payload) if trim_trailing_lf(payload).starts_with(b"ERR ") => {
354 parse_error_line(payload).map(Some)
355 }
356 PktLineFrame::Data(_)
357 | PktLineFrame::Flush
358 | PktLineFrame::Delimiter
359 | PktLineFrame::ResponseEnd => Ok(None),
360 }
361}
362
363pub fn read_error_line(reader: &mut impl Read) -> Result<ProtocolErrorLine> {
364 let Some(frame) = read_pkt_line_frame(reader)? else {
365 return Err(GitError::InvalidFormat(
366 "pkt-line stream ended before protocol error line".into(),
367 ));
368 };
369 match frame {
370 PktLineFrame::Data(payload) => parse_error_line(&payload),
371 _ => Err(GitError::InvalidFormat(
372 "protocol error line must be a data packet".into(),
373 )),
374 }
375}
376
377pub fn write_error_line(writer: &mut impl Write, error: &ProtocolErrorLine) -> Result<()> {
378 write_pkt_line_frame(writer, &PktLineFrame::data(encode_error_line(error)?)?)
379}
380
381pub fn parse_git_service(value: &str) -> Result<GitService> {
382 match value {
383 "git-upload-pack" => Ok(GitService::UploadPack),
384 "git-receive-pack" => Ok(GitService::ReceivePack),
385 "git-upload-archive" => Ok(GitService::UploadArchive),
386 other => Err(GitError::InvalidFormat(format!(
387 "unsupported git service {other}"
388 ))),
389 }
390}
391
392pub fn parse_refspec(value: &str) -> Result<RefSpec> {
393 validate_refspec_value(value)?;
394 let (force, value) = value
395 .strip_prefix('+')
396 .map_or((false, value), |value| (true, value));
397 let (negative, value) = value
398 .strip_prefix('^')
399 .map_or((false, value), |value| (true, value));
400 if force && negative {
401 return Err(GitError::InvalidFormat(
402 "negative refspec must not be forced".into(),
403 ));
404 }
405 let (src, dst) = if negative {
406 if value.contains(':') {
407 return Err(GitError::InvalidFormat(
408 "negative refspec must not have a destination".into(),
409 ));
410 }
411 (Some(value), None)
412 } else if let Some((src, dst)) = value.split_once(':') {
413 (non_empty(src), non_empty(dst))
414 } else {
415 (Some(value), None)
416 };
417 if src.is_none() && dst.is_none() && value != ":" {
418 return Err(GitError::InvalidFormat(
419 "refspec must include a source or destination".into(),
420 ));
421 }
422 if negative && src.is_none() {
423 return Err(GitError::InvalidFormat(
424 "negative refspec is missing a source".into(),
425 ));
426 }
427 if let Some(src) = src {
428 validate_refspec_endpoint("refspec source", src)?;
429 }
430 if let Some(dst) = dst {
431 validate_refspec_endpoint("refspec destination", dst)?;
432 }
433 let src_pattern_count = src.map(count_refspec_wildcards).unwrap_or(0);
434 let dst_pattern_count = dst.map(count_refspec_wildcards).unwrap_or(0);
435 if src_pattern_count > 1 || dst_pattern_count > 1 {
436 return Err(GitError::InvalidFormat(
437 "refspec endpoint has too many wildcards".into(),
438 ));
439 }
440 if dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
441 return Err(GitError::InvalidFormat(
442 "refspec wildcard must appear in both source and destination".into(),
443 ));
444 }
445 Ok(RefSpec {
446 force,
447 negative,
448 src: src.map(str::to_string),
449 dst: dst.map(str::to_string),
450 pattern: src_pattern_count == 1 || dst_pattern_count == 1,
451 })
452}
453
454pub fn encode_refspec(refspec: &RefSpec) -> Result<String> {
455 validate_refspec_shape(refspec)?;
456 let mut out = String::new();
457 if refspec.force {
458 out.push('+');
459 }
460 if refspec.negative {
461 out.push('^');
462 }
463 if let Some(src) = &refspec.src {
464 out.push_str(src);
465 }
466 if !refspec.negative && refspec.src.is_none() && refspec.dst.is_none() {
467 out.push(':');
468 } else if !refspec.negative && refspec.dst.is_some() {
469 out.push(':');
470 if let Some(dst) = &refspec.dst {
471 out.push_str(dst);
472 }
473 }
474 Ok(out)
475}
476
477pub fn refspec_matches_source(refspec: &RefSpec, source: &str) -> Result<bool> {
478 Ok(refspec_map_source(refspec, source)?.is_some())
479}
480
481pub fn refspec_map_source(refspec: &RefSpec, source: &str) -> Result<Option<String>> {
482 validate_refspec_shape(refspec)?;
483 validate_refspec_endpoint("refspec match source", source)?;
484 let Some(src) = refspec.src.as_deref() else {
485 return Ok(None);
486 };
487 if refspec.pattern {
488 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
489 return Ok(None);
490 };
491 let Some(middle) = source
492 .strip_prefix(src_prefix)
493 .and_then(|value| value.strip_suffix(src_suffix))
494 else {
495 return Ok(None);
496 };
497 if let Some(dst) = refspec.dst.as_deref() {
498 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
499 GitError::InvalidFormat("pattern refspec destination is missing wildcard".into())
500 })?;
501 return Ok(Some(format!("{dst_prefix}{middle}{dst_suffix}")));
502 }
503 return Ok(Some(source.to_string()));
504 }
505 if src == source {
506 return Ok(Some(
507 refspec.dst.clone().unwrap_or_else(|| source.to_string()),
508 ));
509 }
510 Ok(None)
511}
512
513pub fn fetch_head_ref_description(refname: &str) -> Result<String> {
514 validate_fetch_head_description_field(refname)?;
515 if refname == "HEAD" {
519 Ok(String::new())
520 } else if let Some(branch) = refname.strip_prefix("refs/heads/") {
521 Ok(format!("branch '{branch}'"))
522 } else if let Some(tag) = refname.strip_prefix("refs/tags/") {
523 Ok(format!("tag '{tag}'"))
524 } else if let Some(rest) = refname.strip_prefix("refs/remotes/") {
525 Ok(format!("remote-tracking branch '{rest}'"))
526 } else {
527 Ok(format!("'{refname}'"))
528 }
529}
530
531pub fn fetch_head_remote_description(refname: &str, remote: &str) -> Result<String> {
532 validate_fetch_head_description_field(remote)?;
533 let what = fetch_head_ref_description(refname)?;
536 if what.is_empty() {
537 Ok(remote.to_string())
538 } else {
539 Ok(format!("{what} of {remote}"))
540 }
541}
542
543pub fn parse_fetch_head(format: ObjectFormat, input: &[u8]) -> Result<Vec<FetchHeadRecord>> {
544 if input.is_empty() {
545 return Ok(Vec::new());
546 }
547 input
548 .split_inclusive(|byte| *byte == b'\n')
549 .map(|line| parse_fetch_head_record(format, line))
550 .collect()
551}
552
553pub fn encode_fetch_head(records: &[FetchHeadRecord]) -> Result<Vec<u8>> {
554 let mut out = Vec::new();
555 for record in records {
556 validate_fetch_head_description_field(&record.description)?;
557 out.extend_from_slice(record.oid.to_string().as_bytes());
558 out.push(b'\t');
559 if record.not_for_merge {
560 out.extend_from_slice(b"not-for-merge");
561 }
562 out.push(b'\t');
563 out.extend_from_slice(record.description.as_bytes());
564 out.push(b'\n');
565 }
566 Ok(out)
567}
568
569pub fn read_fetch_head(
570 format: ObjectFormat,
571 reader: &mut impl Read,
572) -> Result<Vec<FetchHeadRecord>> {
573 let mut input = Vec::new();
574 reader.read_to_end(&mut input)?;
575 parse_fetch_head(format, &input)
576}
577
578pub fn write_fetch_head(writer: &mut impl Write, records: &[FetchHeadRecord]) -> Result<()> {
579 for record in records {
580 validate_fetch_head_description_field(&record.description)?;
581 writer.write_all(record.oid.to_string().as_bytes())?;
582 writer.write_all(b"\t")?;
583 if record.not_for_merge {
584 writer.write_all(b"not-for-merge")?;
585 }
586 writer.write_all(b"\t")?;
587 writer.write_all(record.description.as_bytes())?;
588 writer.write_all(b"\n")?;
589 }
590 Ok(())
591}
592
593fn find_advertised_ref_by_name_abbrev<'a>(
599 refs: &'a [RefAdvertisement],
600 name: &str,
601) -> Option<&'a RefAdvertisement> {
602 let mut best: Option<(&RefAdvertisement, usize)> = None;
603 for reference in refs {
604 let score = fetch_refname_match_score(name, &reference.name);
605 if score > best.map(|(_, score)| score).unwrap_or(0) {
606 best = Some((reference, score));
607 }
608 }
609 best.map(|(reference, _)| reference)
610}
611
612fn fetch_refname_match_score(abbrev: &str, full: &str) -> usize {
615 let expansions = [
616 abbrev.to_string(),
617 format!("refs/{abbrev}"),
618 format!("refs/tags/{abbrev}"),
619 format!("refs/heads/{abbrev}"),
620 format!("refs/remotes/{abbrev}"),
621 format!("refs/remotes/{abbrev}/HEAD"),
622 ];
623 for (index, candidate) in expansions.iter().enumerate() {
624 if candidate == full {
625 return expansions.len() - index;
626 }
627 }
628 0
629}
630
631pub fn refname_matches(abbrev: &str, full: &str) -> bool {
636 fetch_refname_match_score(abbrev, full) > 0
637}
638
639fn fetch_local_ref_name(name: &str) -> String {
643 if name.starts_with("refs/") {
644 name.to_string()
645 } else if name.starts_with("heads/")
646 || name.starts_with("tags/")
647 || name.starts_with("remotes/")
648 {
649 format!("refs/{name}")
650 } else {
651 format!("refs/heads/{name}")
652 }
653}
654
655pub fn plan_fetch_ref_updates(
656 refs: &[RefAdvertisement],
657 refspecs: &[RefSpec],
658 auto_follow_tags: bool,
659) -> Result<Vec<FetchRefUpdate>> {
660 let negative = refspecs
661 .iter()
662 .filter(|refspec| refspec.negative)
663 .collect::<Vec<_>>();
664 let mut updates = Vec::new();
665 for refspec in refspecs.iter().filter(|refspec| !refspec.negative) {
666 validate_refspec_shape(refspec)?;
667 let Some(src) = refspec.src.as_deref() else {
668 return Err(GitError::InvalidFormat(
669 "fetch refspec is missing a source".into(),
670 ));
671 };
672 if refspec.pattern {
673 for reference in refs {
674 if refspec_is_excluded(&negative, &reference.name)? {
675 continue;
676 }
677 if let Some(dst) = refspec_map_source(refspec, &reference.name)? {
678 updates.push(FetchRefUpdate {
679 src: reference.name.clone(),
680 dst: Some(dst),
681 oid: reference.oid,
682 not_for_merge: false,
683 });
684 }
685 }
686 continue;
687 }
688 if refspec_is_excluded(&negative, src)? {
689 continue;
690 }
691 let Some(reference) = find_advertised_ref_by_name_abbrev(refs, src) else {
692 return Err(GitError::reference_not_found(format!("remote ref {src}")));
693 };
694 updates.push(FetchRefUpdate {
695 src: reference.name.clone(),
696 dst: refspec.dst.as_deref().map(fetch_local_ref_name),
697 oid: reference.oid,
698 not_for_merge: false,
699 });
700 }
701 if auto_follow_tags && updates.iter().any(|update| update.dst.is_some()) {
702 let fetched_oids = updates.iter().map(|update| update.oid).collect::<Vec<_>>();
703 let fetched_srcs = updates
704 .iter()
705 .map(|update| update.src.clone())
706 .collect::<Vec<_>>();
707 for reference in refs {
708 if reference.name.starts_with("refs/tags/")
709 && fetched_oids.iter().any(|oid| oid == &reference.oid)
710 && !fetched_srcs.contains(&reference.name)
711 && !refspec_is_excluded(&negative, &reference.name)?
712 {
713 updates.push(FetchRefUpdate {
714 src: reference.name.clone(),
715 dst: Some(reference.name.clone()),
716 oid: reference.oid,
717 not_for_merge: true,
718 });
719 }
720 }
721 }
722 Ok(updates)
723}
724
725pub fn fetch_ref_updates_to_fetch_head(
726 updates: &[FetchRefUpdate],
727 remote: &str,
728) -> Result<Vec<FetchHeadRecord>> {
729 updates
730 .iter()
731 .map(|update| {
732 Ok(FetchHeadRecord {
733 oid: update.oid,
734 not_for_merge: update.not_for_merge,
735 description: fetch_head_remote_description(&update.src, remote)?,
736 })
737 })
738 .collect()
739}
740
741pub fn plan_push_commands(
742 format: ObjectFormat,
743 local_refs: &[PushSourceRef],
744 remote_refs: &[RefAdvertisement],
745 refspecs: &[RefSpec],
746) -> Result<Vec<ReceivePackCommand>> {
747 let zero = zero_object_id(format)?;
748 let mut commands = Vec::new();
749 for refspec in refspecs {
750 validate_refspec_shape(refspec)?;
751 if refspec.negative {
752 return Err(GitError::InvalidFormat(
753 "push refspec must not be negative".into(),
754 ));
755 }
756 match (refspec.src.as_deref(), refspec.dst.as_deref()) {
757 (None, None) => {
758 for local in local_refs {
764 if !local.name.starts_with("refs/") {
765 continue;
766 }
767 validate_push_source_ref(format, local)?;
768 if let Some(remote) = remote_ref(remote_refs, &local.name) {
769 commands.push(ReceivePackCommand {
770 old_id: remote.oid,
771 new_id: local.oid,
772 name: local.name.clone(),
773 });
774 }
775 }
776 }
777 (None, Some(dst)) => {
778 validate_refspec_endpoint("push destination", dst)?;
779 let remote = remote_ref(remote_refs, dst)
780 .ok_or_else(|| GitError::reference_not_found(format!("remote ref {dst}")))?;
781 commands.push(ReceivePackCommand {
782 old_id: remote.oid,
783 new_id: zero.clone(),
784 name: dst.to_string(),
785 });
786 }
787 (Some(src), dst) if refspec.pattern => {
788 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
789 return Err(GitError::InvalidFormat(
790 "pattern push refspec source is missing wildcard".into(),
791 ));
792 };
793 let dst = dst.ok_or_else(|| {
794 GitError::InvalidFormat("pattern push refspec is missing destination".into())
795 })?;
796 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
797 GitError::InvalidFormat(
798 "pattern push refspec destination is missing wildcard".into(),
799 )
800 })?;
801 for local in local_refs {
802 validate_push_source_ref(format, local)?;
803 let Some(middle) = local
804 .name
805 .strip_prefix(src_prefix)
806 .and_then(|value| value.strip_suffix(src_suffix))
807 else {
808 continue;
809 };
810 let name = format!("{dst_prefix}{middle}{dst_suffix}");
811 let old_id = remote_ref(remote_refs, &name)
812 .map(|reference| reference.oid)
813 .unwrap_or_else(|| zero.clone());
814 commands.push(ReceivePackCommand {
815 old_id,
816 new_id: local.oid,
817 name,
818 });
819 }
820 }
821 (Some(src), dst) => {
822 validate_refspec_endpoint("push source", src)?;
823 let local = local_ref(local_refs, src)
824 .ok_or_else(|| GitError::reference_not_found(format!("local ref {src}")))?;
825 validate_push_source_ref(format, local)?;
826 let name = dst.unwrap_or(src);
827 validate_refspec_endpoint("push destination", name)?;
828 let old_id = remote_ref(remote_refs, name)
829 .map(|reference| reference.oid)
830 .unwrap_or_else(|| zero.clone());
831 commands.push(ReceivePackCommand {
832 old_id,
833 new_id: local.oid,
834 name: name.to_string(),
835 });
836 }
837 }
838 }
839 Ok(commands)
840}
841
842pub fn build_receive_pack_push_request(
843 features: &ReceivePackFeatures,
844 commands: Vec<ReceivePackCommand>,
845 packfile: Vec<u8>,
846 options: ReceivePackPushRequestOptions,
847) -> Result<ReceivePackPushRequest> {
848 let mut capabilities = Vec::new();
849 if options.report_status_v2 {
850 require_receive_pack_feature(features.report_status_v2, "report-status-v2")?;
851 capabilities.push(Capability {
852 name: "report-status-v2".into(),
853 value: None,
854 });
855 } else if options.report_status {
856 require_receive_pack_feature(features.report_status, "report-status")?;
857 capabilities.push(Capability {
858 name: "report-status".into(),
859 value: None,
860 });
861 }
862 if commands.iter().any(is_receive_pack_delete_command) {
863 require_receive_pack_feature(features.delete_refs, "delete-refs")?;
864 capabilities.push(Capability {
865 name: "delete-refs".into(),
866 value: None,
867 });
868 }
869 if options.atomic {
870 require_receive_pack_feature(features.atomic, "atomic")?;
871 capabilities.push(Capability {
872 name: "atomic".into(),
873 value: None,
874 });
875 }
876 if options.ofs_delta {
877 require_receive_pack_feature(features.ofs_delta, "ofs-delta")?;
878 capabilities.push(Capability {
879 name: "ofs-delta".into(),
880 value: None,
881 });
882 }
883 if options.side_band_64k {
884 require_receive_pack_feature(features.side_band_64k, "side-band-64k")?;
885 capabilities.push(Capability {
886 name: "side-band-64k".into(),
887 value: None,
888 });
889 }
890 if options.quiet {
891 require_receive_pack_feature(features.quiet, "quiet")?;
892 capabilities.push(Capability {
893 name: "quiet".into(),
894 value: None,
895 });
896 }
897 if let Some(agent) = &options.agent {
898 validate_capability_field("receive-pack request agent", agent)?;
899 capabilities.push(Capability {
900 name: "agent".into(),
901 value: Some(agent.clone()),
902 });
903 }
904 if let Some(format) = options.object_format {
905 if features.object_format != Some(format) {
906 return Err(GitError::InvalidFormat(
907 "receive-pack request object-format was not advertised".into(),
908 ));
909 }
910 capabilities.push(Capability {
911 name: "object-format".into(),
912 value: Some(format.name().into()),
913 });
914 }
915 let push_options = if options.push_options.is_empty() {
916 None
917 } else {
918 require_receive_pack_feature(features.push_options, "push-options")?;
919 for option in &options.push_options {
920 validate_receive_pack_push_option(option.as_bytes())?;
921 }
922 capabilities.push(Capability {
923 name: "push-options".into(),
924 value: None,
925 });
926 Some(options.push_options)
927 };
928 let request = ReceivePackPushRequest {
929 commands: ReceivePackRequest {
930 commands,
931 capabilities,
932 shallow: Vec::new(),
933 },
934 push_options,
935 packfile,
936 };
937 validate_receive_pack_push_request_features(features, &request)?;
938 Ok(request)
939}
940
941pub fn smart_http_info_refs_path(repository_path: &str, service: GitService) -> Result<String> {
942 validate_smart_http_service(service)?;
943 let repository_path = normalize_http_repository_path(repository_path)?;
944 Ok(format!(
945 "{repository_path}/info/refs?service={}",
946 service.as_str()
947 ))
948}
949
950pub fn smart_http_rpc_path(repository_path: &str, service: GitService) -> Result<String> {
951 validate_smart_http_service(service)?;
952 let repository_path = normalize_http_repository_path(repository_path)?;
953 Ok(format!("{repository_path}/{}", service.as_str()))
954}
955
956pub fn dumb_http_info_refs_path(repository_path: &str) -> Result<String> {
957 let repository_path = normalize_http_repository_path(repository_path)?;
958 Ok(format!("{repository_path}/info/refs"))
959}
960
961pub fn dumb_http_alternates_path(repository_path: &str) -> Result<String> {
962 let repository_path = normalize_http_repository_path(repository_path)?;
963 Ok(format!("{repository_path}/objects/info/http-alternates"))
964}
965
966pub fn dumb_http_packs_path(repository_path: &str) -> Result<String> {
967 let repository_path = normalize_http_repository_path(repository_path)?;
968 Ok(format!("{repository_path}/objects/info/packs"))
969}
970
971pub fn dumb_http_loose_object_path(repository_path: &str, oid: &ObjectId) -> Result<String> {
972 let repository_path = normalize_http_repository_path(repository_path)?;
973 let oid = oid.to_string();
974 let (directory, file) = oid.split_at(2);
975 Ok(format!("{repository_path}/objects/{directory}/{file}"))
976}
977
978pub fn dumb_http_pack_file_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
979 dumb_http_pack_resource_path(repository_path, hash, "pack")
980}
981
982pub fn dumb_http_pack_index_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
983 dumb_http_pack_resource_path(repository_path, hash, "idx")
984}
985
986pub fn smart_http_advertisement_content_type(service: GitService) -> Result<String> {
987 validate_smart_http_service(service)?;
988 Ok(format!("application/x-{}-advertisement", service.as_str()))
989}
990
991pub fn smart_http_rpc_request_content_type(service: GitService) -> Result<String> {
992 validate_smart_http_service(service)?;
993 Ok(format!("application/x-{}-request", service.as_str()))
994}
995
996pub fn smart_http_rpc_result_content_type(service: GitService) -> Result<String> {
997 validate_smart_http_service(service)?;
998 Ok(format!("application/x-{}-result", service.as_str()))
999}
1000
1001pub fn parse_smart_http_advertisement_content_type(value: &str) -> Result<GitService> {
1002 parse_smart_http_content_type(value, "-advertisement")
1003}
1004
1005pub fn parse_smart_http_rpc_request_content_type(value: &str) -> Result<GitService> {
1006 parse_smart_http_content_type(value, "-request")
1007}
1008
1009pub fn parse_smart_http_rpc_result_content_type(value: &str) -> Result<GitService> {
1010 parse_smart_http_content_type(value, "-result")
1011}
1012
1013pub fn parse_sideband_packet(payload: &[u8]) -> Result<SideBandPacket> {
1014 let Some((&channel, data)) = payload.split_first() else {
1015 return Err(GitError::InvalidFormat("sideband packet is empty".into()));
1016 };
1017 let channel = match channel {
1018 1 => SideBandChannel::Data,
1019 2 => SideBandChannel::Progress,
1020 3 => SideBandChannel::Fatal,
1021 other => {
1022 return Err(GitError::InvalidFormat(format!(
1023 "invalid sideband channel {other}"
1024 )));
1025 }
1026 };
1027 Ok(SideBandPacket {
1028 channel,
1029 data: data.to_vec(),
1030 })
1031}
1032
1033pub fn encode_sideband_packet(packet: &SideBandPacket) -> Result<Vec<u8>> {
1034 let mut out = Vec::with_capacity(packet.data.len() + 1);
1035 out.push(match packet.channel {
1036 SideBandChannel::Data => 1,
1037 SideBandChannel::Progress => 2,
1038 SideBandChannel::Fatal => 3,
1039 });
1040 out.extend_from_slice(&packet.data);
1041 if out.len() > PKT_LINE_MAX_PAYLOAD_LEN {
1042 return Err(GitError::InvalidFormat(format!(
1043 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1044 )));
1045 }
1046 Ok(out)
1047}
1048
1049pub fn write_sideband_packet(writer: &mut impl Write, packet: &SideBandPacket) -> Result<()> {
1050 write_sideband_payload(writer, packet.channel, &packet.data)
1051}
1052
1053fn write_sideband_payload(
1054 writer: &mut impl Write,
1055 channel: SideBandChannel,
1056 data: &[u8],
1057) -> Result<()> {
1058 let payload_len = data
1059 .len()
1060 .checked_add(1)
1061 .ok_or_else(|| GitError::InvalidFormat("sideband packet length overflow".into()))?;
1062 if payload_len > PKT_LINE_MAX_PAYLOAD_LEN {
1063 return Err(GitError::InvalidFormat(format!(
1064 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1065 )));
1066 }
1067 writer.write_all(&pkt_line_header(payload_len + 4))?;
1068 writer.write_all(&[match channel {
1069 SideBandChannel::Data => 1,
1070 SideBandChannel::Progress => 2,
1071 SideBandChannel::Fatal => 3,
1072 }])?;
1073 writer.write_all(data)?;
1074 Ok(())
1075}
1076
1077pub fn parse_sideband_packets(payloads: &[Vec<u8>]) -> Result<Vec<SideBandPacket>> {
1078 payloads
1079 .iter()
1080 .map(|payload| parse_sideband_packet(payload))
1081 .collect()
1082}
1083
1084pub fn encode_sideband_packets(packets: &[SideBandPacket]) -> Result<Vec<Vec<u8>>> {
1085 packets.iter().map(encode_sideband_packet).collect()
1086}
1087
1088pub fn parse_sideband_stream(frames: &[PktLineFrame]) -> Result<Vec<SideBandPacket>> {
1089 let mut packets = Vec::new();
1090 let mut saw_flush = false;
1091 for (idx, frame) in frames.iter().enumerate() {
1092 match frame {
1093 PktLineFrame::Data(payload) if !saw_flush => {
1094 packets.push(parse_sideband_packet(payload)?);
1095 }
1096 PktLineFrame::Data(_) => {
1097 return Err(GitError::InvalidFormat(
1098 "sideband stream has data after flush".into(),
1099 ));
1100 }
1101 PktLineFrame::Flush => {
1102 saw_flush = true;
1103 if idx + 1 != frames.len() {
1104 return Err(GitError::InvalidFormat(
1105 "sideband stream has frames after flush".into(),
1106 ));
1107 }
1108 }
1109 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1110 return Err(GitError::InvalidFormat(
1111 "sideband stream contains a non-flush control packet".into(),
1112 ));
1113 }
1114 }
1115 }
1116 if !saw_flush {
1117 return Err(GitError::InvalidFormat(
1118 "sideband stream missing flush".into(),
1119 ));
1120 }
1121 Ok(packets)
1122}
1123
1124pub fn encode_sideband_stream(packets: &[SideBandPacket]) -> Result<Vec<PktLineFrame>> {
1125 let mut frames = Vec::new();
1126 for packet in packets {
1127 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
1128 }
1129 frames.push(PktLineFrame::Flush);
1130 Ok(frames)
1131}
1132
1133pub fn read_sideband_stream(reader: &mut impl Read) -> Result<Vec<SideBandPacket>> {
1134 let frames = read_pkt_line_frames_until_flush(reader)?;
1135 parse_sideband_stream(&frames)
1136}
1137
1138pub fn write_sideband_stream(writer: &mut impl Write, packets: &[SideBandPacket]) -> Result<()> {
1139 for packet in packets {
1140 write_sideband_packet(writer, packet)?;
1141 }
1142 writer.write_all(b"0000")?;
1143 Ok(())
1144}
1145
1146pub fn demux_sideband_packets(packets: &[SideBandPacket]) -> Result<SideBandDemux> {
1147 let mut out = SideBandDemux::default();
1148 for packet in packets {
1149 match packet.channel {
1150 SideBandChannel::Data => out.data.extend_from_slice(&packet.data),
1151 SideBandChannel::Progress => out.progress.push(packet.data.clone()),
1152 SideBandChannel::Fatal => {
1153 let message = String::from_utf8_lossy(&packet.data).into_owned();
1154 return Err(GitError::InvalidFormat(format!(
1155 "sideband fatal: {message}"
1156 )));
1157 }
1158 }
1159 }
1160 Ok(out)
1161}
1162
1163pub fn parse_and_demux_sideband_packets(payloads: &[Vec<u8>]) -> Result<SideBandDemux> {
1164 let packets = parse_sideband_packets(payloads)?;
1165 demux_sideband_packets(&packets)
1166}
1167
1168pub fn demux_sideband_stream(frames: &[PktLineFrame]) -> Result<SideBandDemux> {
1169 let packets = parse_sideband_stream(frames)?;
1170 demux_sideband_packets(&packets)
1171}
1172
1173pub fn read_and_demux_sideband_stream(reader: &mut impl Read) -> Result<SideBandDemux> {
1174 let packets = read_sideband_stream(reader)?;
1175 demux_sideband_packets(&packets)
1176}
1177
1178pub fn parse_upload_archive_request(frames: &[PktLineFrame]) -> Result<UploadArchiveRequest> {
1179 let mut request = UploadArchiveRequest::default();
1180 let mut saw_flush = false;
1181 for (idx, frame) in frames.iter().enumerate() {
1182 match frame {
1183 PktLineFrame::Data(payload) if !saw_flush => {
1184 let text = parse_protocol_v2_line_text("upload-archive request argument", payload)?;
1185 let argument = text.strip_prefix("argument ").ok_or_else(|| {
1186 GitError::InvalidFormat("upload-archive request line must be argument".into())
1187 })?;
1188 validate_upload_archive_argument(argument)?;
1189 request.arguments.push(argument.to_string());
1190 }
1191 PktLineFrame::Data(_) => {
1192 return Err(GitError::InvalidFormat(
1193 "upload-archive request has data after flush".into(),
1194 ));
1195 }
1196 PktLineFrame::Flush => {
1197 saw_flush = true;
1198 if idx + 1 != frames.len() {
1199 return Err(GitError::InvalidFormat(
1200 "upload-archive request has frames after flush".into(),
1201 ));
1202 }
1203 }
1204 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1205 return Err(GitError::InvalidFormat(
1206 "upload-archive request contains a non-flush control packet".into(),
1207 ));
1208 }
1209 }
1210 }
1211 if !saw_flush {
1212 return Err(GitError::InvalidFormat(
1213 "upload-archive request missing flush".into(),
1214 ));
1215 }
1216 if request.arguments.is_empty() {
1217 return Err(GitError::InvalidFormat(
1218 "upload-archive request is missing arguments".into(),
1219 ));
1220 }
1221 Ok(request)
1222}
1223
1224pub fn encode_upload_archive_request(request: &UploadArchiveRequest) -> Result<Vec<PktLineFrame>> {
1225 if request.arguments.is_empty() {
1226 return Err(GitError::InvalidFormat(
1227 "upload-archive request is missing arguments".into(),
1228 ));
1229 }
1230 let mut frames = Vec::new();
1231 for argument in &request.arguments {
1232 validate_upload_archive_argument(argument)?;
1233 frames.push(PktLineFrame::data(line_from_str(&format!(
1234 "argument {argument}"
1235 )))?);
1236 }
1237 frames.push(PktLineFrame::Flush);
1238 Ok(frames)
1239}
1240
1241pub fn read_upload_archive_request(reader: &mut impl Read) -> Result<UploadArchiveRequest> {
1242 let frames = read_pkt_line_frames_until_flush(reader)?;
1243 parse_upload_archive_request(&frames)
1244}
1245
1246pub fn write_upload_archive_request(
1247 writer: &mut impl Write,
1248 request: &UploadArchiveRequest,
1249) -> Result<()> {
1250 if request.arguments.is_empty() {
1251 return Err(GitError::InvalidFormat(
1252 "upload-archive request is missing arguments".into(),
1253 ));
1254 }
1255 for argument in &request.arguments {
1256 validate_upload_archive_argument(argument)?;
1257 write_pkt_line_payload(writer, &line_from_str(&format!("argument {argument}")))?;
1258 }
1259 writer.write_all(b"0000")?;
1260 Ok(())
1261}
1262
1263pub fn parse_upload_archive_response(frames: &[PktLineFrame]) -> Result<UploadArchiveResponse> {
1264 let Some((first, rest)) = frames.split_first() else {
1265 return Err(GitError::InvalidFormat(
1266 "upload-archive response is empty".into(),
1267 ));
1268 };
1269 let PktLineFrame::Data(payload) = first else {
1270 return Err(GitError::InvalidFormat(
1271 "upload-archive response must start with a data packet".into(),
1272 ));
1273 };
1274 let text = parse_protocol_v2_line_text("upload-archive response status", payload)?;
1275 if text == "ACK" {
1276 return Ok(UploadArchiveResponse::Ack {
1277 sideband: parse_sideband_stream(rest)?,
1278 });
1279 }
1280 if let Some(message) = text.strip_prefix("NACK ") {
1281 validate_upload_archive_status_message(message)?;
1282 if !matches!(rest, [PktLineFrame::Flush]) {
1283 return Err(GitError::InvalidFormat(
1284 "upload-archive NACK response must end with flush".into(),
1285 ));
1286 }
1287 return Ok(UploadArchiveResponse::Nack {
1288 message: message.to_string(),
1289 });
1290 }
1291 Err(GitError::InvalidFormat(format!(
1292 "unsupported upload-archive response status {text}"
1293 )))
1294}
1295
1296pub fn encode_upload_archive_response(
1297 response: &UploadArchiveResponse,
1298) -> Result<Vec<PktLineFrame>> {
1299 let mut frames = Vec::new();
1300 match response {
1301 UploadArchiveResponse::Ack { sideband } => {
1302 frames.push(PktLineFrame::data(line_from_str("ACK"))?);
1303 frames.extend(encode_sideband_stream(sideband)?);
1304 }
1305 UploadArchiveResponse::Nack { message } => {
1306 validate_upload_archive_status_message(message)?;
1307 frames.push(PktLineFrame::data(line_from_str(&format!(
1308 "NACK {message}"
1309 )))?);
1310 frames.push(PktLineFrame::Flush);
1311 }
1312 }
1313 Ok(frames)
1314}
1315
1316pub fn read_upload_archive_response(reader: &mut impl Read) -> Result<UploadArchiveResponse> {
1317 let frames = read_pkt_line_frames_until_flush(reader)?;
1318 parse_upload_archive_response(&frames)
1319}
1320
1321pub fn write_upload_archive_response(
1322 writer: &mut impl Write,
1323 response: &UploadArchiveResponse,
1324) -> Result<()> {
1325 match response {
1326 UploadArchiveResponse::Ack { sideband } => {
1327 write_pkt_line_payload(writer, b"ACK\n")?;
1328 write_sideband_stream(writer, sideband)?;
1329 }
1330 UploadArchiveResponse::Nack { message } => {
1331 validate_upload_archive_status_message(message)?;
1332 write_pkt_line_payload(writer, &line_from_str(&format!("NACK {message}")))?;
1333 writer.write_all(b"0000")?;
1334 }
1335 }
1336 Ok(())
1337}
1338
1339pub fn demux_upload_archive_response(response: &UploadArchiveResponse) -> Result<SideBandDemux> {
1340 match response {
1341 UploadArchiveResponse::Ack { sideband } => demux_sideband_packets(sideband),
1342 UploadArchiveResponse::Nack { message } => Err(GitError::InvalidFormat(format!(
1343 "upload-archive NACK: {message}"
1344 ))),
1345 }
1346}
1347
1348fn parse_pkt_len(bytes: &[u8]) -> Result<usize> {
1349 let mut len = 0usize;
1350 for byte in bytes {
1351 len = (len << 4) | hex_nibble(*byte)? as usize;
1352 }
1353 Ok(len)
1354}
1355
1356fn hex_nibble(byte: u8) -> Result<u8> {
1357 match byte {
1358 b'0'..=b'9' => Ok(byte - b'0'),
1359 b'a'..=b'f' => Ok(byte - b'a' + 10),
1360 b'A'..=b'F' => Ok(byte - b'A' + 10),
1361 _ => Err(GitError::InvalidFormat(format!(
1362 "invalid pkt-line length byte {byte:#04x}"
1363 ))),
1364 }
1365}
1366
1367#[derive(Debug, Clone, PartialEq, Eq)]
1368pub struct TransportHandshake {
1369 pub protocol: ProtocolVersion,
1370 pub capabilities: Vec<Capability>,
1371}
1372
1373#[derive(Debug, Clone, PartialEq, Eq)]
1374pub struct RefAdvertisement {
1375 pub oid: ObjectId,
1376 pub name: String,
1377 pub capabilities: Vec<Capability>,
1378}
1379
1380#[derive(Debug, Clone, PartialEq, Eq)]
1381pub struct DumbHttpRefRecord {
1382 pub oid: ObjectId,
1383 pub name: String,
1384 pub peeled: bool,
1385}
1386
1387#[derive(Debug, Clone, PartialEq, Eq)]
1388pub struct DumbHttpPackRecord {
1389 pub hash: ObjectId,
1390}
1391
1392#[derive(Debug, Clone, PartialEq, Eq)]
1393pub struct RefAdvertisementSet {
1394 pub protocol: ProtocolVersion,
1395 pub refs: Vec<RefAdvertisement>,
1396 pub shallow: Vec<ObjectId>,
1397}
1398
1399#[derive(Debug, Clone, PartialEq, Eq, Default)]
1400pub struct UploadPackRequest {
1401 pub wants: Vec<ObjectId>,
1402 pub capabilities: Vec<Capability>,
1403 pub shallow: Vec<ObjectId>,
1404 pub deepen: Option<u32>,
1405 pub deepen_since: Option<u64>,
1406 pub deepen_not: Vec<String>,
1407 pub filter: Option<String>,
1408}
1409
1410#[derive(Debug, Clone, PartialEq, Eq, Default)]
1411pub struct UploadPackFeatures {
1412 pub multi_ack: bool,
1413 pub multi_ack_detailed: bool,
1414 pub no_done: bool,
1415 pub thin_pack: bool,
1416 pub side_band: bool,
1417 pub side_band_64k: bool,
1418 pub ofs_delta: bool,
1419 pub shallow: bool,
1420 pub deepen_since: bool,
1421 pub deepen_not: bool,
1422 pub include_tag: bool,
1423 pub no_progress: bool,
1424 pub allow_tip_sha1_in_want: bool,
1425 pub allow_reachable_sha1_in_want: bool,
1426 pub filter: bool,
1427 pub agent: Option<String>,
1428 pub object_format: Option<ObjectFormat>,
1429 pub symrefs: Vec<String>,
1430 pub unknown: Vec<Capability>,
1431}
1432
1433#[derive(Debug, Clone, PartialEq, Eq, Default)]
1434pub struct UploadPackNegotiationRequest {
1435 pub haves: Vec<ObjectId>,
1436 pub done: bool,
1437}
1438
1439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1440pub enum UploadPackAckStatus {
1441 Continue,
1442 Common,
1443 Ready,
1444}
1445
1446#[derive(Debug, Clone, PartialEq, Eq)]
1447pub enum UploadPackAcknowledgment {
1448 Nak,
1449 Ack {
1450 oid: ObjectId,
1451 status: Option<UploadPackAckStatus>,
1452 },
1453}
1454
1455#[derive(Debug, Clone, PartialEq, Eq, Default)]
1456pub struct UploadPackPackfileResponse {
1457 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1458 pub sideband: Vec<SideBandPacket>,
1459}
1460
1461#[derive(Debug, Clone, PartialEq, Eq, Default)]
1462pub struct UploadPackRawPackfileResponse {
1463 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1464 pub packfile: Vec<u8>,
1465}
1466
1467#[derive(Debug, Clone, PartialEq, Eq)]
1468pub struct ReceivePackCommand {
1469 pub old_id: ObjectId,
1470 pub new_id: ObjectId,
1471 pub name: String,
1472}
1473
1474#[derive(Debug, Clone, PartialEq, Eq, Default)]
1475pub struct ReceivePackRequest {
1476 pub shallow: Vec<ObjectId>,
1477 pub commands: Vec<ReceivePackCommand>,
1478 pub capabilities: Vec<Capability>,
1479}
1480
1481#[derive(Debug, Clone, PartialEq, Eq, Default)]
1482pub struct ReceivePackPushRequest {
1483 pub commands: ReceivePackRequest,
1484 pub push_options: Option<Vec<String>>,
1485 pub packfile: Vec<u8>,
1486}
1487
1488#[derive(Debug, Clone, PartialEq, Eq, Default)]
1489pub struct ReceivePackPushRequestOptions {
1490 pub report_status: bool,
1491 pub report_status_v2: bool,
1492 pub atomic: bool,
1493 pub ofs_delta: bool,
1494 pub side_band_64k: bool,
1495 pub quiet: bool,
1496 pub agent: Option<String>,
1497 pub object_format: Option<ObjectFormat>,
1498 pub push_options: Vec<String>,
1499}
1500
1501#[derive(Debug, Clone, PartialEq, Eq, Default)]
1502pub struct ReceivePackFeatures {
1503 pub report_status: bool,
1504 pub report_status_v2: bool,
1505 pub delete_refs: bool,
1506 pub ofs_delta: bool,
1507 pub atomic: bool,
1508 pub push_options: bool,
1509 pub side_band_64k: bool,
1510 pub quiet: bool,
1511 pub no_thin: bool,
1512 pub agent: Option<String>,
1513 pub object_format: Option<ObjectFormat>,
1514 pub unknown: Vec<Capability>,
1515}
1516
1517#[derive(Debug, Clone, PartialEq, Eq)]
1518pub enum ReceivePackUnpackStatus {
1519 Ok,
1520 Error(String),
1521}
1522
1523#[derive(Debug, Clone, PartialEq, Eq)]
1524pub enum ReceivePackCommandStatus {
1525 Ok { name: String },
1526 Ng { name: String, message: String },
1527}
1528
1529#[derive(Debug, Clone, PartialEq, Eq)]
1530pub struct ReceivePackReportStatus {
1531 pub unpack: ReceivePackUnpackStatus,
1532 pub commands: Vec<ReceivePackCommandStatus>,
1533}
1534
1535#[derive(Debug, Clone, PartialEq, Eq, Default)]
1536pub struct ReceivePackCommandStatusV2Options {
1537 pub refname: Option<String>,
1538 pub old_oid: Option<ObjectId>,
1539 pub new_oid: Option<ObjectId>,
1540 pub forced_update: bool,
1541}
1542
1543#[derive(Debug, Clone, PartialEq, Eq)]
1544pub enum ReceivePackCommandStatusV2 {
1545 Ok {
1546 name: String,
1547 options: ReceivePackCommandStatusV2Options,
1548 },
1549 Ng {
1550 name: String,
1551 message: String,
1552 },
1553}
1554
1555#[derive(Debug, Clone, PartialEq, Eq)]
1556pub struct ReceivePackReportStatusV2 {
1557 pub unpack: ReceivePackUnpackStatus,
1558 pub commands: Vec<ReceivePackCommandStatusV2>,
1559}
1560
1561#[derive(Debug, Clone, PartialEq, Eq)]
1562pub struct ProtocolV2CommandRequest {
1563 pub command: String,
1564 pub capabilities: Vec<Capability>,
1565 pub arguments: Vec<Vec<u8>>,
1566}
1567
1568#[derive(Debug, Clone, PartialEq, Eq)]
1569pub enum ProtocolV2Request {
1570 Command(ProtocolV2CommandRequest),
1571 Done,
1572}
1573
1574#[derive(Debug, Clone, PartialEq, Eq)]
1575pub enum ProtocolV2Command {
1576 LsRefs(ProtocolV2LsRefsRequest),
1577 Fetch(ProtocolV2FetchRequest),
1578 ObjectInfo(ProtocolV2ObjectInfoRequest),
1579 Unknown(ProtocolV2CommandRequest),
1580}
1581
1582#[derive(Debug, Clone, PartialEq, Eq)]
1583pub enum ProtocolV2SessionRequest {
1584 Command(ProtocolV2Command),
1585 Done,
1586}
1587
1588#[derive(Debug, Clone, PartialEq, Eq, Default)]
1589pub struct ProtocolV2CommandOptions {
1590 pub agent: Option<String>,
1591 pub object_format: Option<ObjectFormat>,
1592 pub server_options: Vec<String>,
1593 pub extra: Vec<Capability>,
1594}
1595
1596#[derive(Debug, Clone, PartialEq, Eq, Default)]
1597pub struct ProtocolV2FetchFeatures {
1598 pub shallow: bool,
1599 pub wait_for_done: bool,
1600 pub filter: bool,
1601 pub ref_in_want: bool,
1602 pub sideband_all: bool,
1603 pub packfile_uris: bool,
1604 pub unknown: Vec<String>,
1605}
1606
1607#[derive(Debug, Clone, PartialEq, Eq, Default)]
1608pub struct ProtocolV2LsRefsFeatures {
1609 pub unborn: bool,
1610 pub unknown: Vec<String>,
1611}
1612
1613impl ProtocolV2CommandRequest {
1614 pub fn new(command: impl Into<String>) -> Result<Self> {
1615 let command = command.into();
1616 validate_capability_name(&command)?;
1617 Ok(Self {
1618 command,
1619 capabilities: Vec::new(),
1620 arguments: Vec::new(),
1621 })
1622 }
1623}
1624
1625#[derive(Debug, Clone, PartialEq, Eq, Default)]
1626pub struct ProtocolV2LsRefsRequest {
1627 pub peel: bool,
1628 pub symrefs: bool,
1629 pub unborn: bool,
1630 pub ref_prefixes: Vec<String>,
1631}
1632
1633#[derive(Debug, Clone, PartialEq, Eq)]
1634pub struct ProtocolV2LsRefsRef {
1635 pub oid: ObjectId,
1636 pub name: String,
1637 pub peeled: Option<ObjectId>,
1638 pub symref_target: Option<String>,
1639 pub attributes: Vec<String>,
1640}
1641
1642#[derive(Debug, Clone, PartialEq, Eq)]
1643pub enum ProtocolV2LsRefsRecord {
1644 Ref(ProtocolV2LsRefsRef),
1645 Unborn {
1646 name: String,
1647 symref_target: Option<String>,
1648 attributes: Vec<String>,
1649 },
1650}
1651
1652#[derive(Debug, Clone, PartialEq, Eq, Default)]
1653pub struct ProtocolV2FetchRequest {
1654 pub wants: Vec<ObjectId>,
1655 pub want_refs: Vec<String>,
1656 pub haves: Vec<ObjectId>,
1657 pub shallow: Vec<ObjectId>,
1658 pub deepen: Option<u32>,
1659 pub deepen_since: Option<u64>,
1660 pub deepen_not: Vec<String>,
1661 pub deepen_relative: bool,
1662 pub filter: Option<String>,
1663 pub packfile_uris: Option<String>,
1664 pub thin_pack: bool,
1665 pub no_progress: bool,
1666 pub include_tag: bool,
1667 pub ofs_delta: bool,
1668 pub sideband_all: bool,
1669 pub wait_for_done: bool,
1670 pub done: bool,
1671}
1672
1673#[derive(Debug, Clone, PartialEq, Eq)]
1674pub enum ProtocolV2FetchAcknowledgment {
1675 Nak,
1676 Ack(ObjectId),
1677 Ready,
1678}
1679
1680#[derive(Debug, Clone, PartialEq, Eq)]
1681pub enum ProtocolV2FetchShallowInfo {
1682 Shallow(ObjectId),
1683 Unshallow(ObjectId),
1684}
1685
1686#[derive(Debug, Clone, PartialEq, Eq)]
1687pub struct ProtocolV2FetchWantedRef {
1688 pub oid: ObjectId,
1689 pub name: String,
1690}
1691
1692#[derive(Debug, Clone, PartialEq, Eq)]
1693pub struct ProtocolV2FetchPackfileUri {
1694 pub pack_hash: ObjectId,
1695 pub uri: String,
1696}
1697
1698#[derive(Debug, Clone, PartialEq, Eq)]
1699pub enum ProtocolV2FetchResponseSection {
1700 Acknowledgments(Vec<ProtocolV2FetchAcknowledgment>),
1701 ShallowInfo(Vec<ProtocolV2FetchShallowInfo>),
1702 WantedRefs(Vec<ProtocolV2FetchWantedRef>),
1703 PackfileUris(Vec<ProtocolV2FetchPackfileUri>),
1704 Packfile(Vec<Vec<u8>>),
1705 Unknown { name: String, lines: Vec<Vec<u8>> },
1706}
1707
1708#[derive(Debug, Clone, PartialEq, Eq, Default)]
1709pub struct ProtocolV2FetchSidebandAllResponse {
1710 pub sections: Vec<ProtocolV2FetchResponseSection>,
1711 pub progress: Vec<Vec<u8>>,
1712}
1713
1714#[derive(Debug, Clone, PartialEq, Eq, Default)]
1715pub struct ProtocolV2ObjectInfoRequest {
1716 pub size: bool,
1717 pub oids: Vec<ObjectId>,
1718}
1719
1720#[derive(Debug, Clone, PartialEq, Eq)]
1721pub struct ProtocolV2ObjectInfoRecord {
1722 pub oid: ObjectId,
1723 pub size: u64,
1724}
1725
1726#[derive(Debug, Clone, PartialEq, Eq, Default)]
1727pub struct ProtocolV2ObjectInfoResponse {
1728 pub size: bool,
1729 pub records: Vec<ProtocolV2ObjectInfoRecord>,
1730}
1731
1732impl ProtocolV2LsRefsRequest {
1733 pub fn from_command_request(request: &ProtocolV2CommandRequest) -> Result<Self> {
1734 if request.command != "ls-refs" {
1735 return Err(GitError::InvalidFormat(format!(
1736 "expected ls-refs command, got {}",
1737 request.command
1738 )));
1739 }
1740 let mut out = Self::default();
1741 for argument in &request.arguments {
1742 let text = std::str::from_utf8(argument)
1743 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1744 match text {
1745 "peel" => out.peel = true,
1746 "symrefs" => out.symrefs = true,
1747 "unborn" => out.unborn = true,
1748 value if value.starts_with("ref-prefix ") => {
1749 let prefix = value
1750 .strip_prefix("ref-prefix ")
1751 .ok_or_else(|| GitError::InvalidFormat("invalid ref-prefix".into()))?;
1752 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1753 out.ref_prefixes.push(prefix.to_string());
1754 }
1755 other => {
1756 return Err(GitError::InvalidFormat(format!(
1757 "unsupported ls-refs argument {other}"
1758 )));
1759 }
1760 }
1761 }
1762 Ok(out)
1763 }
1764
1765 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1766 let mut request = ProtocolV2CommandRequest::new("ls-refs")?;
1767 if self.peel {
1768 request.arguments.push(b"peel".to_vec());
1769 }
1770 if self.symrefs {
1771 request.arguments.push(b"symrefs".to_vec());
1772 }
1773 if self.unborn {
1774 request.arguments.push(b"unborn".to_vec());
1775 }
1776 for prefix in &self.ref_prefixes {
1777 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1778 request
1779 .arguments
1780 .push(format!("ref-prefix {prefix}").into_bytes());
1781 }
1782 Ok(request)
1783 }
1784}
1785
1786impl ProtocolV2FetchRequest {
1787 pub fn from_command_request(
1788 format: ObjectFormat,
1789 request: &ProtocolV2CommandRequest,
1790 ) -> Result<Self> {
1791 if request.command != "fetch" {
1792 return Err(GitError::InvalidFormat(format!(
1793 "expected fetch command, got {}",
1794 request.command
1795 )));
1796 }
1797 let mut out = Self::default();
1798 for argument in &request.arguments {
1799 let text = std::str::from_utf8(argument)
1800 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1801 match text {
1802 "thin-pack" => out.thin_pack = true,
1803 "no-progress" => out.no_progress = true,
1804 "include-tag" => out.include_tag = true,
1805 "ofs-delta" => out.ofs_delta = true,
1806 "sideband-all" => out.sideband_all = true,
1807 "wait-for-done" => out.wait_for_done = true,
1808 "deepen-relative" => out.deepen_relative = true,
1809 "done" => out.done = true,
1810 value if value.starts_with("want ") => {
1811 out.wants
1812 .push(parse_oid_argument(format, "fetch want", value, "want ")?);
1813 }
1814 value if value.starts_with("want-ref ") => {
1815 let name = value
1816 .strip_prefix("want-ref ")
1817 .ok_or_else(|| GitError::InvalidFormat("invalid fetch want-ref".into()))?;
1818 validate_protocol_v2_token("fetch want-ref", name)?;
1819 out.want_refs.push(name.to_string());
1820 }
1821 value if value.starts_with("have ") => {
1822 out.haves
1823 .push(parse_oid_argument(format, "fetch have", value, "have ")?);
1824 }
1825 value if value.starts_with("shallow ") => {
1826 out.shallow.push(parse_oid_argument(
1827 format,
1828 "fetch shallow",
1829 value,
1830 "shallow ",
1831 )?);
1832 }
1833 value if value.starts_with("deepen ") => {
1834 if out.deepen.is_some() {
1835 return Err(GitError::InvalidFormat(
1836 "fetch request has duplicate deepen".into(),
1837 ));
1838 }
1839 out.deepen = Some(parse_u32_argument("fetch deepen", value, "deepen ")?);
1840 }
1841 value if value.starts_with("deepen-since ") => {
1842 if out.deepen_since.is_some() {
1843 return Err(GitError::InvalidFormat(
1844 "fetch request has duplicate deepen-since".into(),
1845 ));
1846 }
1847 out.deepen_since = Some(parse_u64_argument(
1848 "fetch deepen-since",
1849 value,
1850 "deepen-since ",
1851 )?);
1852 }
1853 value if value.starts_with("deepen-not ") => {
1854 let name = value.strip_prefix("deepen-not ").ok_or_else(|| {
1855 GitError::InvalidFormat("invalid fetch deepen-not".into())
1856 })?;
1857 validate_protocol_v2_token("fetch deepen-not", name)?;
1858 out.deepen_not.push(name.to_string());
1859 }
1860 value if value.starts_with("filter ") => {
1861 if out.filter.is_some() {
1862 return Err(GitError::InvalidFormat(
1863 "fetch request has duplicate filter".into(),
1864 ));
1865 }
1866 let filter = value
1867 .strip_prefix("filter ")
1868 .ok_or_else(|| GitError::InvalidFormat("invalid fetch filter".into()))?;
1869 validate_protocol_v2_token("fetch filter", filter)?;
1870 out.filter = Some(filter.to_string());
1871 }
1872 value if value.starts_with("packfile-uris ") => {
1873 if out.packfile_uris.is_some() {
1874 return Err(GitError::InvalidFormat(
1875 "fetch request has duplicate packfile-uris".into(),
1876 ));
1877 }
1878 let protocols = value.strip_prefix("packfile-uris ").ok_or_else(|| {
1879 GitError::InvalidFormat("invalid fetch packfile-uris".into())
1880 })?;
1881 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
1882 out.packfile_uris = Some(protocols.to_string());
1883 }
1884 other => {
1885 return Err(GitError::InvalidFormat(format!(
1886 "unsupported fetch argument {other}"
1887 )));
1888 }
1889 }
1890 }
1891 Ok(out)
1892 }
1893
1894 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1895 let mut request = ProtocolV2CommandRequest::new("fetch")?;
1896 for oid in &self.wants {
1897 request.arguments.push(format!("want {oid}").into_bytes());
1898 }
1899 for name in &self.want_refs {
1900 validate_protocol_v2_token("fetch want-ref", name)?;
1901 request
1902 .arguments
1903 .push(format!("want-ref {name}").into_bytes());
1904 }
1905 for oid in &self.haves {
1906 request.arguments.push(format!("have {oid}").into_bytes());
1907 }
1908 for oid in &self.shallow {
1909 request
1910 .arguments
1911 .push(format!("shallow {oid}").into_bytes());
1912 }
1913 if let Some(deepen) = self.deepen {
1914 if deepen == 0 {
1915 return Err(GitError::InvalidFormat(
1916 "fetch deepen must be positive".into(),
1917 ));
1918 }
1919 request
1920 .arguments
1921 .push(format!("deepen {deepen}").into_bytes());
1922 }
1923 if let Some(deepen_since) = self.deepen_since {
1924 request
1925 .arguments
1926 .push(format!("deepen-since {deepen_since}").into_bytes());
1927 }
1928 for name in &self.deepen_not {
1929 validate_protocol_v2_token("fetch deepen-not", name)?;
1930 request
1931 .arguments
1932 .push(format!("deepen-not {name}").into_bytes());
1933 }
1934 if self.deepen_relative {
1935 request.arguments.push(b"deepen-relative".to_vec());
1936 }
1937 if let Some(filter) = &self.filter {
1938 validate_protocol_v2_token("fetch filter", filter)?;
1939 request
1940 .arguments
1941 .push(format!("filter {filter}").into_bytes());
1942 }
1943 if let Some(protocols) = &self.packfile_uris {
1944 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
1945 request
1946 .arguments
1947 .push(format!("packfile-uris {protocols}").into_bytes());
1948 }
1949 if self.thin_pack {
1950 request.arguments.push(b"thin-pack".to_vec());
1951 }
1952 if self.no_progress {
1953 request.arguments.push(b"no-progress".to_vec());
1954 }
1955 if self.include_tag {
1956 request.arguments.push(b"include-tag".to_vec());
1957 }
1958 if self.ofs_delta {
1959 request.arguments.push(b"ofs-delta".to_vec());
1960 }
1961 if self.sideband_all {
1962 request.arguments.push(b"sideband-all".to_vec());
1963 }
1964 if self.wait_for_done {
1965 request.arguments.push(b"wait-for-done".to_vec());
1966 }
1967 if self.done {
1968 request.arguments.push(b"done".to_vec());
1969 }
1970 Ok(request)
1971 }
1972}
1973
1974impl ProtocolV2ObjectInfoRequest {
1975 pub fn from_command_request(
1976 format: ObjectFormat,
1977 request: &ProtocolV2CommandRequest,
1978 ) -> Result<Self> {
1979 if request.command != "object-info" {
1980 return Err(GitError::InvalidFormat(format!(
1981 "expected object-info command, got {}",
1982 request.command
1983 )));
1984 }
1985 let mut out = Self::default();
1986 for argument in &request.arguments {
1987 let text = parse_protocol_v2_line_text("object-info request argument", argument)?;
1988 if text == "size" {
1989 if out.size {
1990 return Err(GitError::InvalidFormat(
1991 "object-info request has duplicate size argument".into(),
1992 ));
1993 }
1994 out.size = true;
1995 } else if text.starts_with("oid ") {
1996 out.oids
1997 .push(parse_oid_argument(format, "object-info oid", text, "oid ")?);
1998 } else {
1999 return Err(GitError::InvalidFormat(format!(
2000 "unsupported object-info request argument {text}"
2001 )));
2002 }
2003 }
2004 if !out.size {
2005 return Err(GitError::InvalidFormat(
2006 "object-info request is missing size argument".into(),
2007 ));
2008 }
2009 if out.oids.is_empty() {
2010 return Err(GitError::InvalidFormat(
2011 "object-info request is missing object ids".into(),
2012 ));
2013 }
2014 Ok(out)
2015 }
2016
2017 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
2018 if !self.size {
2019 return Err(GitError::InvalidFormat(
2020 "object-info request is missing size argument".into(),
2021 ));
2022 }
2023 if self.oids.is_empty() {
2024 return Err(GitError::InvalidFormat(
2025 "object-info request is missing object ids".into(),
2026 ));
2027 }
2028 let mut request = ProtocolV2CommandRequest::new("object-info")?;
2029 request.arguments.push(b"size".to_vec());
2030 for oid in &self.oids {
2031 request.arguments.push(format!("oid {oid}").into_bytes());
2032 }
2033 Ok(request)
2034 }
2035}
2036
2037pub fn parse_protocol_v2_advertisement(frames: &[PktLineFrame]) -> Result<TransportHandshake> {
2038 let Some((first, rest)) = frames.split_first() else {
2039 return Err(GitError::InvalidFormat(
2040 "protocol v2 advertisement is empty".into(),
2041 ));
2042 };
2043 match first {
2044 PktLineFrame::Data(payload) if trim_trailing_lf(payload) == b"version 2" => {}
2045 PktLineFrame::Data(_) => {
2046 return Err(GitError::InvalidFormat(
2047 "protocol v2 advertisement missing version line".into(),
2048 ));
2049 }
2050 _ => {
2051 return Err(GitError::InvalidFormat(
2052 "protocol v2 advertisement must start with a data line".into(),
2053 ));
2054 }
2055 }
2056
2057 let mut capabilities = Vec::new();
2058 let mut saw_flush = false;
2059 for (idx, frame) in rest.iter().enumerate() {
2060 match frame {
2061 PktLineFrame::Data(payload) => {
2062 if saw_flush {
2063 return Err(GitError::InvalidFormat(
2064 "protocol v2 advertisement has data after flush".into(),
2065 ));
2066 }
2067 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2068 }
2069 PktLineFrame::Flush => {
2070 saw_flush = true;
2071 if idx + 1 != rest.len() {
2072 return Err(GitError::InvalidFormat(
2073 "protocol v2 advertisement has frames after flush".into(),
2074 ));
2075 }
2076 }
2077 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2078 return Err(GitError::InvalidFormat(
2079 "protocol v2 advertisement contains a non-flush control packet".into(),
2080 ));
2081 }
2082 }
2083 }
2084 if !saw_flush {
2085 return Err(GitError::InvalidFormat(
2086 "protocol v2 advertisement missing flush".into(),
2087 ));
2088 }
2089
2090 Ok(TransportHandshake {
2091 protocol: ProtocolVersion::V2,
2092 capabilities,
2093 })
2094}
2095
2096pub fn encode_protocol_v2_advertisement(
2097 handshake: &TransportHandshake,
2098) -> Result<Vec<PktLineFrame>> {
2099 if handshake.protocol != ProtocolVersion::V2 {
2100 return Err(GitError::InvalidFormat(
2101 "protocol v2 advertisement requires a v2 handshake".into(),
2102 ));
2103 }
2104 let mut frames = vec![PktLineFrame::data(line_from_str("version 2"))?];
2105 for capability in &handshake.capabilities {
2106 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2107 capability,
2108 )?))?);
2109 }
2110 frames.push(PktLineFrame::Flush);
2111 Ok(frames)
2112}
2113
2114pub fn read_protocol_v2_advertisement(reader: &mut impl Read) -> Result<TransportHandshake> {
2115 let frames = read_pkt_line_frames_until_flush(reader)?;
2116 parse_protocol_v2_advertisement(&frames)
2117}
2118
2119pub fn write_protocol_v2_advertisement(
2120 writer: &mut impl Write,
2121 handshake: &TransportHandshake,
2122) -> Result<()> {
2123 if handshake.protocol != ProtocolVersion::V2 {
2124 return Err(GitError::InvalidFormat(
2125 "protocol v2 advertisement requires a v2 handshake".into(),
2126 ));
2127 }
2128 write_pkt_line_payload(writer, b"version 2\n")?;
2129 for capability in &handshake.capabilities {
2130 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2131 }
2132 writer.write_all(b"0000")?;
2133 Ok(())
2134}
2135
2136pub fn parse_protocol_v2_command_request(
2137 frames: &[PktLineFrame],
2138) -> Result<ProtocolV2CommandRequest> {
2139 let Some((first, rest)) = frames.split_first() else {
2140 return Err(GitError::InvalidFormat(
2141 "protocol v2 command request is empty".into(),
2142 ));
2143 };
2144 let command = match first {
2145 PktLineFrame::Data(payload) => parse_protocol_v2_command_line(payload)?,
2146 _ => {
2147 return Err(GitError::InvalidFormat(
2148 "protocol v2 command request must start with a command line".into(),
2149 ));
2150 }
2151 };
2152
2153 let mut capabilities = Vec::new();
2154 let mut arguments = Vec::new();
2155 let mut in_arguments = false;
2156 let mut saw_flush = false;
2157 for (idx, frame) in rest.iter().enumerate() {
2158 match frame {
2159 PktLineFrame::Data(payload) if !in_arguments => {
2160 if saw_flush {
2161 return Err(GitError::InvalidFormat(
2162 "protocol v2 command request has data after flush".into(),
2163 ));
2164 }
2165 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2166 }
2167 PktLineFrame::Data(payload) => {
2168 if saw_flush {
2169 return Err(GitError::InvalidFormat(
2170 "protocol v2 command request has data after flush".into(),
2171 ));
2172 }
2173 let argument = trim_trailing_lf(payload);
2174 if argument.is_empty() {
2175 return Err(GitError::InvalidFormat(
2176 "protocol v2 command argument is empty".into(),
2177 ));
2178 }
2179 if argument
2180 .iter()
2181 .any(|byte| matches!(*byte, b'\n' | b'\r' | 0))
2182 {
2183 return Err(GitError::InvalidFormat(
2184 "protocol v2 command argument contains a delimiter byte".into(),
2185 ));
2186 }
2187 arguments.push(argument.to_vec());
2188 }
2189 PktLineFrame::Delimiter => {
2190 if in_arguments {
2191 return Err(GitError::InvalidFormat(
2192 "protocol v2 command request has duplicate delimiter".into(),
2193 ));
2194 }
2195 if saw_flush {
2196 return Err(GitError::InvalidFormat(
2197 "protocol v2 command request has delimiter after flush".into(),
2198 ));
2199 }
2200 in_arguments = true;
2201 }
2202 PktLineFrame::Flush => {
2203 saw_flush = true;
2204 if idx + 1 != rest.len() {
2205 return Err(GitError::InvalidFormat(
2206 "protocol v2 command request has frames after flush".into(),
2207 ));
2208 }
2209 }
2210 PktLineFrame::ResponseEnd => {
2211 return Err(GitError::InvalidFormat(
2212 "protocol v2 command request contains response-end".into(),
2213 ));
2214 }
2215 }
2216 }
2217 if !saw_flush {
2218 return Err(GitError::InvalidFormat(
2219 "protocol v2 command request missing flush".into(),
2220 ));
2221 }
2222
2223 Ok(ProtocolV2CommandRequest {
2224 command,
2225 capabilities,
2226 arguments,
2227 })
2228}
2229
2230pub fn encode_protocol_v2_command_request(
2231 request: &ProtocolV2CommandRequest,
2232) -> Result<Vec<PktLineFrame>> {
2233 validate_capability_name(&request.command)?;
2234 let mut frames = Vec::new();
2235 frames.push(PktLineFrame::data(line_from_str(&format!(
2236 "command={}",
2237 request.command
2238 )))?);
2239 for capability in &request.capabilities {
2240 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2241 capability,
2242 )?))?);
2243 }
2244 if !request.arguments.is_empty() {
2245 frames.push(PktLineFrame::Delimiter);
2246 for argument in &request.arguments {
2247 validate_protocol_v2_argument(argument)?;
2248 let mut payload = argument.clone();
2249 payload.push(b'\n');
2250 frames.push(PktLineFrame::data(payload)?);
2251 }
2252 }
2253 frames.push(PktLineFrame::Flush);
2254 Ok(frames)
2255}
2256
2257pub fn parse_protocol_v2_request(frames: &[PktLineFrame]) -> Result<ProtocolV2Request> {
2258 if matches!(frames, [PktLineFrame::Flush]) {
2259 return Ok(ProtocolV2Request::Done);
2260 }
2261 parse_protocol_v2_command_request(frames).map(ProtocolV2Request::Command)
2262}
2263
2264pub fn encode_protocol_v2_request(request: &ProtocolV2Request) -> Result<Vec<PktLineFrame>> {
2265 match request {
2266 ProtocolV2Request::Command(command) => encode_protocol_v2_command_request(command),
2267 ProtocolV2Request::Done => Ok(vec![PktLineFrame::Flush]),
2268 }
2269}
2270
2271pub fn read_protocol_v2_request(reader: &mut impl Read) -> Result<ProtocolV2Request> {
2272 let frames = read_pkt_line_frames_until_flush(reader)?;
2273 parse_protocol_v2_request(&frames)
2274}
2275
2276pub fn write_protocol_v2_request(
2277 writer: &mut impl Write,
2278 request: &ProtocolV2Request,
2279) -> Result<()> {
2280 match request {
2281 ProtocolV2Request::Command(command) => write_protocol_v2_command_request(writer, command),
2282 ProtocolV2Request::Done => {
2283 writer.write_all(b"0000")?;
2284 Ok(())
2285 }
2286 }
2287}
2288
2289pub fn read_protocol_v2_command_request(
2290 reader: &mut impl Read,
2291) -> Result<ProtocolV2CommandRequest> {
2292 let frames = read_pkt_line_frames_until_flush(reader)?;
2293 parse_protocol_v2_command_request(&frames)
2294}
2295
2296pub fn write_protocol_v2_command_request(
2297 writer: &mut impl Write,
2298 request: &ProtocolV2CommandRequest,
2299) -> Result<()> {
2300 validate_capability_name(&request.command)?;
2301 write_pkt_line_payload(
2302 writer,
2303 &line_from_str(&format!("command={}", request.command)),
2304 )?;
2305 for capability in &request.capabilities {
2306 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2307 }
2308 if !request.arguments.is_empty() {
2309 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2310 for argument in &request.arguments {
2311 validate_protocol_v2_argument(argument)?;
2312 let mut payload = argument.clone();
2313 payload.push(b'\n');
2314 write_pkt_line_payload(writer, &payload)?;
2315 }
2316 }
2317 writer.write_all(b"0000")?;
2318 Ok(())
2319}
2320
2321pub fn read_protocol_v2_ls_refs_request(reader: &mut impl Read) -> Result<ProtocolV2LsRefsRequest> {
2322 let request = read_protocol_v2_command_request(reader)?;
2323 ProtocolV2LsRefsRequest::from_command_request(&request)
2324}
2325
2326pub fn write_protocol_v2_ls_refs_request(
2327 writer: &mut impl Write,
2328 request: &ProtocolV2LsRefsRequest,
2329) -> Result<()> {
2330 let command = request.to_command_request()?;
2331 write_protocol_v2_command_request(writer, &command)
2332}
2333
2334pub fn parse_protocol_v2_ls_refs_response(
2335 format: ObjectFormat,
2336 frames: &[PktLineFrame],
2337) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2338 let mut records = Vec::new();
2339 let mut saw_flush = false;
2340 for (idx, frame) in frames.iter().enumerate() {
2341 match frame {
2342 PktLineFrame::Data(payload) => {
2343 if saw_flush {
2344 return Err(GitError::InvalidFormat(
2345 "ls-refs response has data after flush".into(),
2346 ));
2347 }
2348 records.push(parse_protocol_v2_ls_refs_line(format, payload)?);
2349 }
2350 PktLineFrame::Flush => {
2351 saw_flush = true;
2352 if !flush_terminates_protocol_v2_response(frames, idx) {
2353 return Err(GitError::InvalidFormat(
2354 "ls-refs response has frames after flush".into(),
2355 ));
2356 }
2357 }
2358 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2359 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2360 return Err(GitError::InvalidFormat(
2361 "ls-refs response contains a non-flush control packet".into(),
2362 ));
2363 }
2364 }
2365 }
2366 if !saw_flush {
2367 return Err(GitError::InvalidFormat(
2368 "ls-refs response missing flush".into(),
2369 ));
2370 }
2371 Ok(records)
2372}
2373
2374pub fn encode_protocol_v2_ls_refs_response(
2375 records: &[ProtocolV2LsRefsRecord],
2376) -> Result<Vec<PktLineFrame>> {
2377 let mut frames = Vec::new();
2378 for record in records {
2379 frames.push(PktLineFrame::data(line_from_str(
2380 &format_protocol_v2_ls_refs_record(record)?,
2381 ))?);
2382 }
2383 frames.push(PktLineFrame::Flush);
2384 Ok(frames)
2385}
2386
2387pub fn read_protocol_v2_ls_refs_response(
2388 format: ObjectFormat,
2389 reader: &mut impl Read,
2390) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2391 let frames = read_pkt_line_frames_until_flush(reader)?;
2392 parse_protocol_v2_ls_refs_response(format, &frames)
2393}
2394
2395pub fn write_protocol_v2_ls_refs_response(
2396 writer: &mut impl Write,
2397 records: &[ProtocolV2LsRefsRecord],
2398) -> Result<()> {
2399 for record in records {
2400 write_pkt_line_payload(
2401 writer,
2402 &line_from_str(&format_protocol_v2_ls_refs_record(record)?),
2403 )?;
2404 }
2405 writer.write_all(b"0000")?;
2406 Ok(())
2407}
2408
2409pub fn read_protocol_v2_ls_refs_response_until_response_end(
2410 format: ObjectFormat,
2411 reader: &mut impl Read,
2412) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2413 let frames = read_pkt_line_frames_until_response_end(reader)?;
2414 parse_protocol_v2_ls_refs_response(format, &frames)
2415}
2416
2417pub fn write_protocol_v2_ls_refs_response_with_response_end(
2418 writer: &mut impl Write,
2419 records: &[ProtocolV2LsRefsRecord],
2420) -> Result<()> {
2421 write_protocol_v2_ls_refs_response(writer, records)?;
2422 writer.write_all(b"0002")?;
2423 Ok(())
2424}
2425
2426pub fn exchange_protocol_v2_ls_refs(
2427 format: ObjectFormat,
2428 reader: &mut impl Read,
2429 writer: &mut impl Write,
2430 request: &ProtocolV2LsRefsRequest,
2431) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2432 write_protocol_v2_ls_refs_request(writer, request)?;
2433 writer.flush()?;
2434 read_protocol_v2_ls_refs_response(format, reader)
2435}
2436
2437pub fn protocol_v2_ls_refs_records_to_ref_advertisement_set(
2453 records: &[ProtocolV2LsRefsRecord],
2454) -> Result<RefAdvertisementSet> {
2455 let mut refs: Vec<RefAdvertisement> = Vec::new();
2456 let mut symrefs: Vec<Capability> = Vec::new();
2457 for record in records {
2458 match record {
2459 ProtocolV2LsRefsRecord::Ref(reference) => {
2460 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
2461 refs.push(RefAdvertisement {
2462 oid: reference.oid,
2463 name: reference.name.clone(),
2464 capabilities: Vec::new(),
2465 });
2466 if let Some(peeled) = &reference.peeled {
2467 refs.push(RefAdvertisement {
2468 oid: peeled.clone(),
2469 name: format!("{}^{{}}", reference.name),
2470 capabilities: Vec::new(),
2471 });
2472 }
2473 if let Some(target) = &reference.symref_target {
2474 symrefs.push(protocol_v2_symref_capability(&reference.name, target)?);
2475 }
2476 }
2477 ProtocolV2LsRefsRecord::Unborn {
2478 name,
2479 symref_target,
2480 ..
2481 } => {
2482 validate_protocol_v2_token("ls-refs ref name", name)?;
2483 if let Some(target) = symref_target {
2484 symrefs.push(protocol_v2_symref_capability(name, target)?);
2485 }
2486 }
2487 }
2488 }
2489 if !symrefs.is_empty() {
2490 if let Some(first) = refs.first_mut() {
2491 first.capabilities = symrefs;
2492 } else {
2493 return Err(GitError::InvalidFormat(
2494 "ls-refs response advertised symrefs without any concrete refs".into(),
2495 ));
2496 }
2497 }
2498 Ok(RefAdvertisementSet {
2499 protocol: ProtocolVersion::V2,
2500 refs,
2501 shallow: Vec::new(),
2502 })
2503}
2504
2505pub fn parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2510 format: ObjectFormat,
2511 frames: &[PktLineFrame],
2512) -> Result<RefAdvertisementSet> {
2513 let records = parse_protocol_v2_ls_refs_response(format, frames)?;
2514 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2515}
2516
2517pub fn read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2520 format: ObjectFormat,
2521 reader: &mut impl Read,
2522) -> Result<RefAdvertisementSet> {
2523 let records = read_protocol_v2_ls_refs_response(format, reader)?;
2524 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2525}
2526
2527fn protocol_v2_symref_capability(name: &str, target: &str) -> Result<Capability> {
2528 validate_protocol_v2_token("ls-refs symref-target", target)?;
2529 Ok(Capability {
2530 name: "symref".into(),
2531 value: Some(format!("{name}:{target}")),
2532 })
2533}
2534
2535pub fn read_protocol_v2_fetch_request(
2536 format: ObjectFormat,
2537 reader: &mut impl Read,
2538) -> Result<ProtocolV2FetchRequest> {
2539 let request = read_protocol_v2_command_request(reader)?;
2540 ProtocolV2FetchRequest::from_command_request(format, &request)
2541}
2542
2543pub fn write_protocol_v2_fetch_request(
2544 writer: &mut impl Write,
2545 request: &ProtocolV2FetchRequest,
2546) -> Result<()> {
2547 let command = request.to_command_request()?;
2548 write_protocol_v2_command_request(writer, &command)
2549}
2550
2551pub fn read_protocol_v2_object_info_request(
2552 format: ObjectFormat,
2553 reader: &mut impl Read,
2554) -> Result<ProtocolV2ObjectInfoRequest> {
2555 let request = read_protocol_v2_command_request(reader)?;
2556 ProtocolV2ObjectInfoRequest::from_command_request(format, &request)
2557}
2558
2559pub fn write_protocol_v2_object_info_request(
2560 writer: &mut impl Write,
2561 request: &ProtocolV2ObjectInfoRequest,
2562) -> Result<()> {
2563 let command = request.to_command_request()?;
2564 write_protocol_v2_command_request(writer, &command)
2565}
2566
2567pub fn parse_protocol_v2_fetch_response(
2568 format: ObjectFormat,
2569 frames: &[PktLineFrame],
2570) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2571 let mut sections = Vec::new();
2572 let mut current: Option<(String, Vec<Vec<u8>>)> = None;
2573 let mut saw_flush = false;
2574 for (idx, frame) in frames.iter().enumerate() {
2575 match frame {
2576 PktLineFrame::Data(payload) => {
2577 if saw_flush {
2578 return Err(GitError::InvalidFormat(
2579 "fetch response has data after flush".into(),
2580 ));
2581 }
2582 if let Some((_name, lines)) = &mut current {
2583 lines.push(payload.clone());
2584 } else {
2585 let name = parse_fetch_section_header(payload)?;
2586 current = Some((name, Vec::new()));
2587 }
2588 }
2589 PktLineFrame::Delimiter => {
2590 if saw_flush {
2591 return Err(GitError::InvalidFormat(
2592 "fetch response has delimiter after flush".into(),
2593 ));
2594 }
2595 let Some((name, lines)) = current.take() else {
2596 return Err(GitError::InvalidFormat(
2597 "fetch response has delimiter before section".into(),
2598 ));
2599 };
2600 sections.push(parse_fetch_section(format, name, lines)?);
2601 }
2602 PktLineFrame::Flush => {
2603 saw_flush = true;
2604 if !flush_terminates_protocol_v2_response(frames, idx) {
2605 return Err(GitError::InvalidFormat(
2606 "fetch response has frames after flush".into(),
2607 ));
2608 }
2609 if let Some((name, lines)) = current.take() {
2610 sections.push(parse_fetch_section(format, name, lines)?);
2611 }
2612 }
2613 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2614 PktLineFrame::ResponseEnd => {
2615 return Err(GitError::InvalidFormat(
2616 "fetch response contains response-end".into(),
2617 ));
2618 }
2619 }
2620 }
2621 if !saw_flush {
2622 return Err(GitError::InvalidFormat(
2623 "fetch response missing flush".into(),
2624 ));
2625 }
2626 Ok(sections)
2627}
2628
2629pub fn encode_protocol_v2_fetch_response(
2630 sections: &[ProtocolV2FetchResponseSection],
2631) -> Result<Vec<PktLineFrame>> {
2632 let mut frames = Vec::new();
2633 for (idx, section) in sections.iter().enumerate() {
2634 if idx != 0 {
2635 frames.push(PktLineFrame::Delimiter);
2636 }
2637 frames.push(PktLineFrame::data(line_from_str(
2638 protocol_v2_fetch_section_name(section),
2639 ))?);
2640 for line in format_protocol_v2_fetch_section_lines(section)? {
2641 frames.push(PktLineFrame::data(line)?);
2642 }
2643 }
2644 frames.push(PktLineFrame::Flush);
2645 Ok(frames)
2646}
2647
2648pub fn parse_protocol_v2_fetch_sideband_all_response(
2649 format: ObjectFormat,
2650 frames: &[PktLineFrame],
2651) -> Result<ProtocolV2FetchSidebandAllResponse> {
2652 let mut demuxed = Vec::new();
2653 let mut progress = Vec::new();
2654 let mut in_packfile = false;
2655 for frame in frames {
2656 match frame {
2657 PktLineFrame::Data(payload) if in_packfile => {
2658 demuxed.push(PktLineFrame::Data(payload.clone()));
2659 }
2660 PktLineFrame::Data(payload) => {
2661 let packet = parse_sideband_packet(payload)?;
2662 match packet.channel {
2663 SideBandChannel::Data => {
2664 if trim_trailing_lf(&packet.data) == b"packfile" {
2665 in_packfile = true;
2666 }
2667 demuxed.push(PktLineFrame::Data(packet.data));
2668 }
2669 SideBandChannel::Progress => progress.push(packet.data),
2670 SideBandChannel::Fatal => {
2671 let message = String::from_utf8_lossy(&packet.data).into_owned();
2672 return Err(GitError::InvalidFormat(format!(
2673 "sideband fatal: {message}"
2674 )));
2675 }
2676 }
2677 }
2678 PktLineFrame::Delimiter => {
2679 in_packfile = false;
2680 demuxed.push(PktLineFrame::Delimiter);
2681 }
2682 PktLineFrame::Flush => {
2683 in_packfile = false;
2684 demuxed.push(PktLineFrame::Flush);
2685 }
2686 PktLineFrame::ResponseEnd => {
2687 in_packfile = false;
2688 demuxed.push(PktLineFrame::ResponseEnd);
2689 }
2690 }
2691 }
2692 Ok(ProtocolV2FetchSidebandAllResponse {
2693 sections: parse_protocol_v2_fetch_response(format, &demuxed)?,
2694 progress,
2695 })
2696}
2697
2698pub fn encode_protocol_v2_fetch_sideband_all_response(
2699 sections: &[ProtocolV2FetchResponseSection],
2700) -> Result<Vec<PktLineFrame>> {
2701 let frames = encode_protocol_v2_fetch_response(sections)?;
2702 let mut encoded = Vec::new();
2703 let mut in_packfile = false;
2704 for frame in frames {
2705 match frame {
2706 PktLineFrame::Data(payload) if in_packfile => {
2707 encoded.push(PktLineFrame::Data(payload));
2708 }
2709 PktLineFrame::Data(payload) => {
2710 if trim_trailing_lf(&payload) == b"packfile" {
2711 in_packfile = true;
2712 }
2713 encoded.push(PktLineFrame::data(encode_sideband_packet(
2714 &SideBandPacket {
2715 channel: SideBandChannel::Data,
2716 data: payload,
2717 },
2718 )?)?);
2719 }
2720 PktLineFrame::Delimiter => {
2721 in_packfile = false;
2722 encoded.push(PktLineFrame::Delimiter);
2723 }
2724 PktLineFrame::Flush => {
2725 in_packfile = false;
2726 encoded.push(PktLineFrame::Flush);
2727 }
2728 PktLineFrame::ResponseEnd => {
2729 in_packfile = false;
2730 encoded.push(PktLineFrame::ResponseEnd);
2731 }
2732 }
2733 }
2734 Ok(encoded)
2735}
2736
2737pub fn read_protocol_v2_fetch_response(
2738 format: ObjectFormat,
2739 reader: &mut impl Read,
2740) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2741 let frames = read_pkt_line_frames_until_flush(reader)?;
2742 parse_protocol_v2_fetch_response(format, &frames)
2743}
2744
2745pub fn write_protocol_v2_fetch_response(
2746 writer: &mut impl Write,
2747 sections: &[ProtocolV2FetchResponseSection],
2748) -> Result<()> {
2749 write_protocol_v2_fetch_response_inner(writer, sections, false, false)
2750}
2751
2752pub fn read_protocol_v2_fetch_sideband_all_response(
2753 format: ObjectFormat,
2754 reader: &mut impl Read,
2755) -> Result<ProtocolV2FetchSidebandAllResponse> {
2756 let frames = read_pkt_line_frames_until_flush(reader)?;
2757 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2758}
2759
2760pub fn write_protocol_v2_fetch_sideband_all_response(
2761 writer: &mut impl Write,
2762 sections: &[ProtocolV2FetchResponseSection],
2763) -> Result<()> {
2764 write_protocol_v2_fetch_response_inner(writer, sections, true, false)
2765}
2766
2767pub fn read_protocol_v2_fetch_response_until_response_end(
2768 format: ObjectFormat,
2769 reader: &mut impl Read,
2770) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2771 let frames = read_pkt_line_frames_until_response_end(reader)?;
2772 parse_protocol_v2_fetch_response(format, &frames)
2773}
2774
2775pub fn write_protocol_v2_fetch_response_with_response_end(
2776 writer: &mut impl Write,
2777 sections: &[ProtocolV2FetchResponseSection],
2778) -> Result<()> {
2779 write_protocol_v2_fetch_response_inner(writer, sections, false, true)
2780}
2781
2782pub fn read_protocol_v2_fetch_sideband_all_response_until_response_end(
2783 format: ObjectFormat,
2784 reader: &mut impl Read,
2785) -> Result<ProtocolV2FetchSidebandAllResponse> {
2786 let frames = read_pkt_line_frames_until_response_end(reader)?;
2787 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2788}
2789
2790pub fn write_protocol_v2_fetch_sideband_all_response_with_response_end(
2791 writer: &mut impl Write,
2792 sections: &[ProtocolV2FetchResponseSection],
2793) -> Result<()> {
2794 write_protocol_v2_fetch_response_inner(writer, sections, true, true)
2795}
2796
2797fn write_protocol_v2_fetch_response_inner(
2798 writer: &mut impl Write,
2799 sections: &[ProtocolV2FetchResponseSection],
2800 sideband_all: bool,
2801 response_end: bool,
2802) -> Result<()> {
2803 let mut in_packfile = false;
2804 for (idx, section) in sections.iter().enumerate() {
2805 if idx != 0 {
2806 in_packfile = false;
2807 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2808 }
2809 write_protocol_v2_fetch_payload(
2810 writer,
2811 &line_from_str(protocol_v2_fetch_section_name(section)),
2812 sideband_all,
2813 &mut in_packfile,
2814 )?;
2815 for payload in format_protocol_v2_fetch_section_lines(section)? {
2816 write_protocol_v2_fetch_payload(writer, &payload, sideband_all, &mut in_packfile)?;
2817 }
2818 }
2819 writer.write_all(b"0000")?;
2820 if response_end {
2821 writer.write_all(b"0002")?;
2822 }
2823 Ok(())
2824}
2825
2826fn write_protocol_v2_fetch_payload(
2827 writer: &mut impl Write,
2828 payload: &[u8],
2829 sideband_all: bool,
2830 in_packfile: &mut bool,
2831) -> Result<()> {
2832 if sideband_all && !*in_packfile {
2833 if trim_trailing_lf(payload) == b"packfile" {
2834 *in_packfile = true;
2835 }
2836 write_sideband_payload(writer, SideBandChannel::Data, payload)
2837 } else {
2838 write_pkt_line_payload(writer, payload)
2839 }
2840}
2841
2842pub fn exchange_protocol_v2_fetch(
2843 format: ObjectFormat,
2844 reader: &mut impl Read,
2845 writer: &mut impl Write,
2846 request: &ProtocolV2FetchRequest,
2847) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2848 write_protocol_v2_fetch_request(writer, request)?;
2849 writer.flush()?;
2850 read_protocol_v2_fetch_response(format, reader)
2851}
2852
2853pub fn parse_protocol_v2_object_info_response(
2854 format: ObjectFormat,
2855 frames: &[PktLineFrame],
2856) -> Result<ProtocolV2ObjectInfoResponse> {
2857 let Some((first, rest)) = frames.split_first() else {
2858 return Err(GitError::InvalidFormat(
2859 "object-info response is empty".into(),
2860 ));
2861 };
2862 let PktLineFrame::Data(attrs) = first else {
2863 return Err(GitError::InvalidFormat(
2864 "object-info response must start with attributes".into(),
2865 ));
2866 };
2867 let attrs = parse_protocol_v2_line_text("object-info response attributes", attrs)?;
2868 let mut response = ProtocolV2ObjectInfoResponse::default();
2869 for attr in attrs.split(' ') {
2870 validate_protocol_v2_token("object-info response attribute", attr)?;
2871 match attr {
2872 "size" => {
2873 if response.size {
2874 return Err(GitError::InvalidFormat(
2875 "object-info response has duplicate size attribute".into(),
2876 ));
2877 }
2878 response.size = true;
2879 }
2880 other => {
2881 return Err(GitError::InvalidFormat(format!(
2882 "unsupported object-info response attribute {other}"
2883 )));
2884 }
2885 }
2886 }
2887 if !response.size {
2888 return Err(GitError::InvalidFormat(
2889 "object-info response is missing size attribute".into(),
2890 ));
2891 }
2892
2893 let mut saw_flush = false;
2894 for (idx, frame) in rest.iter().enumerate() {
2895 match frame {
2896 PktLineFrame::Data(payload) if !saw_flush => {
2897 response
2898 .records
2899 .push(parse_protocol_v2_object_info_record(format, payload)?);
2900 }
2901 PktLineFrame::Data(_) => {
2902 return Err(GitError::InvalidFormat(
2903 "object-info response has data after flush".into(),
2904 ));
2905 }
2906 PktLineFrame::Flush => {
2907 saw_flush = true;
2908 if idx + 1 != rest.len() {
2909 return Err(GitError::InvalidFormat(
2910 "object-info response has frames after flush".into(),
2911 ));
2912 }
2913 }
2914 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2915 return Err(GitError::InvalidFormat(
2916 "object-info response contains a non-flush control packet".into(),
2917 ));
2918 }
2919 }
2920 }
2921 if !saw_flush {
2922 return Err(GitError::InvalidFormat(
2923 "object-info response missing flush".into(),
2924 ));
2925 }
2926 Ok(response)
2927}
2928
2929pub fn encode_protocol_v2_object_info_response(
2930 response: &ProtocolV2ObjectInfoResponse,
2931) -> Result<Vec<PktLineFrame>> {
2932 if !response.size {
2933 return Err(GitError::InvalidFormat(
2934 "object-info response is missing size attribute".into(),
2935 ));
2936 }
2937 let mut frames = Vec::new();
2938 frames.push(PktLineFrame::data(line_from_str("size"))?);
2939 for record in &response.records {
2940 frames.push(PktLineFrame::data(line_from_str(&format!(
2941 "{} {}",
2942 record.oid, record.size
2943 )))?);
2944 }
2945 frames.push(PktLineFrame::Flush);
2946 Ok(frames)
2947}
2948
2949pub fn read_protocol_v2_object_info_response(
2950 format: ObjectFormat,
2951 reader: &mut impl Read,
2952) -> Result<ProtocolV2ObjectInfoResponse> {
2953 let frames = read_pkt_line_frames_until_flush(reader)?;
2954 parse_protocol_v2_object_info_response(format, &frames)
2955}
2956
2957pub fn write_protocol_v2_object_info_response(
2958 writer: &mut impl Write,
2959 response: &ProtocolV2ObjectInfoResponse,
2960) -> Result<()> {
2961 if !response.size {
2962 return Err(GitError::InvalidFormat(
2963 "object-info response is missing size attribute".into(),
2964 ));
2965 }
2966 write_pkt_line_payload(writer, b"size\n")?;
2967 for record in &response.records {
2968 write_pkt_line_payload(
2969 writer,
2970 &line_from_str(&format!("{} {}", record.oid, record.size)),
2971 )?;
2972 }
2973 writer.write_all(b"0000")?;
2974 Ok(())
2975}
2976
2977pub fn exchange_protocol_v2_object_info(
2978 format: ObjectFormat,
2979 reader: &mut impl Read,
2980 writer: &mut impl Write,
2981 request: &ProtocolV2ObjectInfoRequest,
2982) -> Result<ProtocolV2ObjectInfoResponse> {
2983 write_protocol_v2_object_info_request(writer, request)?;
2984 writer.flush()?;
2985 read_protocol_v2_object_info_response(format, reader)
2986}
2987
2988pub fn demux_protocol_v2_fetch_packfile(
2989 sections: &[ProtocolV2FetchResponseSection],
2990) -> Result<Option<SideBandDemux>> {
2991 let mut packfile = None;
2992 for section in sections {
2993 if let ProtocolV2FetchResponseSection::Packfile(lines) = section {
2994 if packfile.is_some() {
2995 return Err(GitError::InvalidFormat(
2996 "fetch response has duplicate packfile sections".into(),
2997 ));
2998 }
2999 packfile = Some(parse_and_demux_sideband_packets(lines)?);
3000 }
3001 }
3002 Ok(packfile)
3003}
3004
3005pub fn protocol_v2_object_format(capabilities: &[Capability]) -> Result<ObjectFormat> {
3006 let mut format = None;
3007 for capability in capabilities {
3008 if capability.name != "object-format" {
3009 continue;
3010 }
3011 if format.is_some() {
3012 return Err(GitError::InvalidFormat(
3013 "protocol v2 has duplicate object-format capabilities".into(),
3014 ));
3015 }
3016 let Some(value) = &capability.value else {
3017 return Err(GitError::InvalidFormat(
3018 "protocol v2 object-format capability is missing a value".into(),
3019 ));
3020 };
3021 format = Some(value.parse::<ObjectFormat>()?);
3022 }
3023 Ok(format.unwrap_or(ObjectFormat::Sha1))
3024}
3025
3026pub fn validate_protocol_v2_command_request_capabilities(
3027 handshake: &TransportHandshake,
3028 request: &ProtocolV2CommandRequest,
3029) -> Result<()> {
3030 if handshake.protocol != ProtocolVersion::V2 {
3031 return Err(GitError::InvalidFormat(
3032 "protocol v2 command validation requires a v2 handshake".into(),
3033 ));
3034 }
3035 let advertised =
3036 protocol_v2_capability(&handshake.capabilities, &request.command).ok_or_else(|| {
3037 GitError::InvalidFormat(format!("unadvertised command {}", request.command))
3038 })?;
3039 if advertised.name.is_empty() {
3040 return Err(GitError::InvalidFormat(
3041 "advertised command capability is empty".into(),
3042 ));
3043 }
3044 parse_protocol_v2_command_options(&request.capabilities)?;
3045
3046 for capability in &request.capabilities {
3047 let advertised = protocol_v2_capability(&handshake.capabilities, &capability.name)
3048 .ok_or_else(|| {
3049 GitError::InvalidFormat(format!(
3050 "unadvertised protocol v2 capability {}",
3051 capability.name
3052 ))
3053 })?;
3054 if capability.name == "object-format" {
3055 validate_protocol_v2_object_format_request(advertised, capability)?;
3056 }
3057 }
3058 Ok(())
3059}
3060
3061pub fn parse_protocol_v2_command_options(
3062 capabilities: &[Capability],
3063) -> Result<ProtocolV2CommandOptions> {
3064 let mut out = ProtocolV2CommandOptions::default();
3065 for capability in capabilities {
3066 match capability.name.as_str() {
3067 "agent" => {
3068 if out.agent.is_some() {
3069 return Err(GitError::InvalidFormat(
3070 "protocol v2 command has duplicate agent capabilities".into(),
3071 ));
3072 }
3073 let Some(value) = &capability.value else {
3074 return Err(GitError::InvalidFormat(
3075 "protocol v2 agent capability is missing a value".into(),
3076 ));
3077 };
3078 validate_protocol_v2_capability_value(value)?;
3079 out.agent = Some(value.clone());
3080 }
3081 "object-format" => {
3082 if out.object_format.is_some() {
3083 return Err(GitError::InvalidFormat(
3084 "protocol v2 command has duplicate object-format capabilities".into(),
3085 ));
3086 }
3087 let Some(value) = &capability.value else {
3088 return Err(GitError::InvalidFormat(
3089 "protocol v2 object-format capability is missing a value".into(),
3090 ));
3091 };
3092 out.object_format = Some(value.parse::<ObjectFormat>()?);
3093 }
3094 "server-option" => {
3095 let Some(value) = &capability.value else {
3096 return Err(GitError::InvalidFormat(
3097 "protocol v2 server-option capability is missing a value".into(),
3098 ));
3099 };
3100 validate_protocol_v2_capability_value(value)?;
3101 out.server_options.push(value.clone());
3102 }
3103 _ => out.extra.push(capability.clone()),
3104 }
3105 }
3106 Ok(out)
3107}
3108
3109pub fn encode_protocol_v2_command_options(
3110 options: &ProtocolV2CommandOptions,
3111) -> Result<Vec<Capability>> {
3112 let mut capabilities = Vec::new();
3113 if let Some(agent) = &options.agent {
3114 validate_protocol_v2_capability_value(agent)?;
3115 capabilities.push(Capability {
3116 name: "agent".into(),
3117 value: Some(agent.clone()),
3118 });
3119 }
3120 if let Some(format) = options.object_format {
3121 capabilities.push(Capability {
3122 name: "object-format".into(),
3123 value: Some(format.name().into()),
3124 });
3125 }
3126 for option in &options.server_options {
3127 validate_protocol_v2_capability_value(option)?;
3128 capabilities.push(Capability {
3129 name: "server-option".into(),
3130 value: Some(option.clone()),
3131 });
3132 }
3133 for capability in &options.extra {
3134 if matches!(
3135 capability.name.as_str(),
3136 "agent" | "object-format" | "server-option"
3137 ) {
3138 return Err(GitError::InvalidFormat(format!(
3139 "protocol v2 extra capability duplicates known capability {}",
3140 capability.name
3141 )));
3142 }
3143 encode_protocol_v2_capability(capability)?;
3144 capabilities.push(capability.clone());
3145 }
3146 Ok(capabilities)
3147}
3148
3149pub fn parse_protocol_v2_ls_refs_features(
3150 capabilities: &[Capability],
3151) -> Result<Option<ProtocolV2LsRefsFeatures>> {
3152 let mut ls_refs = None;
3153 for capability in capabilities {
3154 if capability.name != "ls-refs" {
3155 continue;
3156 }
3157 if ls_refs.is_some() {
3158 return Err(GitError::InvalidFormat(
3159 "protocol v2 has duplicate ls-refs capabilities".into(),
3160 ));
3161 }
3162 ls_refs = Some(parse_protocol_v2_ls_refs_feature_value(
3163 capability.value.as_deref(),
3164 )?);
3165 }
3166 Ok(ls_refs)
3167}
3168
3169pub fn encode_protocol_v2_ls_refs_capability(
3170 features: &ProtocolV2LsRefsFeatures,
3171) -> Result<Capability> {
3172 let mut values = Vec::new();
3173 if features.unborn {
3174 values.push("unborn".to_string());
3175 }
3176 for feature in &features.unknown {
3177 validate_protocol_v2_token("ls-refs feature", feature)?;
3178 if feature == "unborn" {
3179 return Err(GitError::InvalidFormat(
3180 "ls-refs unknown features must not duplicate known feature unborn".into(),
3181 ));
3182 }
3183 values.push(feature.clone());
3184 }
3185 Ok(Capability {
3186 name: "ls-refs".into(),
3187 value: (!values.is_empty()).then(|| values.join(" ")),
3188 })
3189}
3190
3191pub fn validate_protocol_v2_ls_refs_request_features(
3192 features: &ProtocolV2LsRefsFeatures,
3193 request: &ProtocolV2LsRefsRequest,
3194) -> Result<()> {
3195 if request.unborn && !features.unborn {
3196 return Err(GitError::InvalidFormat(
3197 "ls-refs request uses unborn without advertised unborn feature".into(),
3198 ));
3199 }
3200 Ok(())
3201}
3202
3203pub fn validate_protocol_v2_ls_refs_command_request(
3204 handshake: &TransportHandshake,
3205 request: &ProtocolV2CommandRequest,
3206) -> Result<ProtocolV2LsRefsRequest> {
3207 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3208 let ls_refs = ProtocolV2LsRefsRequest::from_command_request(request)?;
3209 let features = parse_protocol_v2_ls_refs_features(&handshake.capabilities)?
3210 .ok_or_else(|| GitError::InvalidFormat("ls-refs command was not advertised".into()))?;
3211 validate_protocol_v2_ls_refs_request_features(&features, &ls_refs)?;
3212 Ok(ls_refs)
3213}
3214
3215pub fn parse_protocol_v2_fetch_features(
3216 capabilities: &[Capability],
3217) -> Result<Option<ProtocolV2FetchFeatures>> {
3218 let mut fetch = None;
3219 for capability in capabilities {
3220 if capability.name != "fetch" {
3221 continue;
3222 }
3223 if fetch.is_some() {
3224 return Err(GitError::InvalidFormat(
3225 "protocol v2 has duplicate fetch capabilities".into(),
3226 ));
3227 }
3228 fetch = Some(parse_protocol_v2_fetch_feature_value(
3229 capability.value.as_deref(),
3230 )?);
3231 }
3232 Ok(fetch)
3233}
3234
3235pub fn encode_protocol_v2_fetch_capability(
3236 features: &ProtocolV2FetchFeatures,
3237) -> Result<Capability> {
3238 let mut values = Vec::new();
3239 if features.shallow {
3240 values.push("shallow".to_string());
3241 }
3242 if features.wait_for_done {
3243 values.push("wait-for-done".to_string());
3244 }
3245 if features.filter {
3246 values.push("filter".to_string());
3247 }
3248 if features.ref_in_want {
3249 values.push("ref-in-want".to_string());
3250 }
3251 if features.sideband_all {
3252 values.push("sideband-all".to_string());
3253 }
3254 if features.packfile_uris {
3255 values.push("packfile-uris".to_string());
3256 }
3257 for feature in &features.unknown {
3258 validate_protocol_v2_token("fetch feature", feature)?;
3259 if matches!(
3260 feature.as_str(),
3261 "shallow"
3262 | "wait-for-done"
3263 | "filter"
3264 | "ref-in-want"
3265 | "sideband-all"
3266 | "packfile-uris"
3267 ) {
3268 return Err(GitError::InvalidFormat(format!(
3269 "fetch unknown features must not duplicate known feature {feature}"
3270 )));
3271 }
3272 values.push(feature.clone());
3273 }
3274 Ok(Capability {
3275 name: "fetch".into(),
3276 value: (!values.is_empty()).then(|| values.join(" ")),
3277 })
3278}
3279
3280pub fn validate_protocol_v2_fetch_request_features(
3281 features: &ProtocolV2FetchFeatures,
3282 request: &ProtocolV2FetchRequest,
3283) -> Result<()> {
3284 if !features.shallow
3285 && (!request.shallow.is_empty()
3286 || request.deepen.is_some()
3287 || request.deepen_since.is_some()
3288 || !request.deepen_not.is_empty()
3289 || request.deepen_relative)
3290 {
3291 return Err(GitError::InvalidFormat(
3292 "fetch request uses shallow/deepen arguments without advertised shallow feature".into(),
3293 ));
3294 }
3295 if !features.filter && request.filter.is_some() {
3296 return Err(GitError::InvalidFormat(
3297 "fetch request uses filter without advertised filter feature".into(),
3298 ));
3299 }
3300 if !features.ref_in_want && !request.want_refs.is_empty() {
3301 return Err(GitError::InvalidFormat(
3302 "fetch request uses want-ref without advertised ref-in-want feature".into(),
3303 ));
3304 }
3305 if !features.sideband_all && request.sideband_all {
3306 return Err(GitError::InvalidFormat(
3307 "fetch request uses sideband-all without advertised sideband-all feature".into(),
3308 ));
3309 }
3310 if !features.packfile_uris && request.packfile_uris.is_some() {
3311 return Err(GitError::InvalidFormat(
3312 "fetch request uses packfile-uris without advertised packfile-uris feature".into(),
3313 ));
3314 }
3315 if !features.wait_for_done && request.wait_for_done {
3316 return Err(GitError::InvalidFormat(
3317 "fetch request uses wait-for-done without advertised wait-for-done feature".into(),
3318 ));
3319 }
3320 Ok(())
3321}
3322
3323pub fn validate_protocol_v2_fetch_command_request(
3324 handshake: &TransportHandshake,
3325 format: ObjectFormat,
3326 request: &ProtocolV2CommandRequest,
3327) -> Result<ProtocolV2FetchRequest> {
3328 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3329 let fetch = ProtocolV2FetchRequest::from_command_request(format, request)?;
3330 let features = parse_protocol_v2_fetch_features(&handshake.capabilities)?
3331 .ok_or_else(|| GitError::InvalidFormat("fetch command was not advertised".into()))?;
3332 validate_protocol_v2_fetch_request_features(&features, &fetch)?;
3333 Ok(fetch)
3334}
3335
3336pub fn validate_protocol_v2_object_info_command_request(
3337 handshake: &TransportHandshake,
3338 format: ObjectFormat,
3339 request: &ProtocolV2CommandRequest,
3340) -> Result<ProtocolV2ObjectInfoRequest> {
3341 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3342 let object_info = ProtocolV2ObjectInfoRequest::from_command_request(format, request)?;
3343 protocol_v2_capability(&handshake.capabilities, "object-info")
3344 .ok_or_else(|| GitError::InvalidFormat("object-info command was not advertised".into()))?;
3345 Ok(object_info)
3346}
3347
3348pub fn classify_protocol_v2_command_request(
3349 handshake: &TransportHandshake,
3350 format: ObjectFormat,
3351 request: &ProtocolV2CommandRequest,
3352) -> Result<ProtocolV2Command> {
3353 match request.command.as_str() {
3354 "ls-refs" => validate_protocol_v2_ls_refs_command_request(handshake, request)
3355 .map(ProtocolV2Command::LsRefs),
3356 "fetch" => validate_protocol_v2_fetch_command_request(handshake, format, request)
3357 .map(ProtocolV2Command::Fetch),
3358 "object-info" => {
3359 validate_protocol_v2_object_info_command_request(handshake, format, request)
3360 .map(ProtocolV2Command::ObjectInfo)
3361 }
3362 _ => {
3363 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3364 Ok(ProtocolV2Command::Unknown(request.clone()))
3365 }
3366 }
3367}
3368
3369pub fn classify_protocol_v2_request(
3370 handshake: &TransportHandshake,
3371 format: ObjectFormat,
3372 request: &ProtocolV2Request,
3373) -> Result<ProtocolV2SessionRequest> {
3374 match request {
3375 ProtocolV2Request::Command(command) => {
3376 classify_protocol_v2_command_request(handshake, format, command)
3377 .map(ProtocolV2SessionRequest::Command)
3378 }
3379 ProtocolV2Request::Done => Ok(ProtocolV2SessionRequest::Done),
3380 }
3381}
3382
3383pub fn read_protocol_v2_session_request(
3384 handshake: &TransportHandshake,
3385 format: ObjectFormat,
3386 reader: &mut impl Read,
3387) -> Result<ProtocolV2SessionRequest> {
3388 let request = read_protocol_v2_request(reader)?;
3389 classify_protocol_v2_request(handshake, format, &request)
3390}
3391
3392fn protocol_v2_capability<'a>(
3393 capabilities: &'a [Capability],
3394 name: &str,
3395) -> Option<&'a Capability> {
3396 capabilities
3397 .iter()
3398 .find(|capability| capability.name == name)
3399}
3400
3401fn validate_protocol_v2_object_format_request(
3402 advertised: &Capability,
3403 requested: &Capability,
3404) -> Result<()> {
3405 let Some(advertised) = &advertised.value else {
3406 return Err(GitError::InvalidFormat(
3407 "advertised object-format capability is missing a value".into(),
3408 ));
3409 };
3410 let Some(requested) = &requested.value else {
3411 return Err(GitError::InvalidFormat(
3412 "requested object-format capability is missing a value".into(),
3413 ));
3414 };
3415 if advertised != requested {
3416 return Err(GitError::InvalidFormat(format!(
3417 "requested object-format {requested} does not match advertised {advertised}"
3418 )));
3419 }
3420 Ok(())
3421}
3422
3423fn parse_protocol_v2_ls_refs_feature_value(
3424 value: Option<&str>,
3425) -> Result<ProtocolV2LsRefsFeatures> {
3426 let mut out = ProtocolV2LsRefsFeatures::default();
3427 let Some(value) = value else {
3428 return Ok(out);
3429 };
3430 if value.is_empty() {
3431 return Err(GitError::InvalidFormat(
3432 "protocol v2 ls-refs capability value is empty".into(),
3433 ));
3434 }
3435 for feature in value.split(' ') {
3436 validate_protocol_v2_token("ls-refs feature", feature)?;
3437 match feature {
3438 "unborn" => out.unborn = true,
3439 other => out.unknown.push(other.to_string()),
3440 }
3441 }
3442 Ok(out)
3443}
3444
3445fn parse_protocol_v2_fetch_feature_value(value: Option<&str>) -> Result<ProtocolV2FetchFeatures> {
3446 let mut out = ProtocolV2FetchFeatures::default();
3447 let Some(value) = value else {
3448 return Ok(out);
3449 };
3450 if value.is_empty() {
3451 return Err(GitError::InvalidFormat(
3452 "protocol v2 fetch capability value is empty".into(),
3453 ));
3454 }
3455 for feature in value.split(' ') {
3456 validate_protocol_v2_token("fetch feature", feature)?;
3457 match feature {
3458 "shallow" => out.shallow = true,
3459 "wait-for-done" => out.wait_for_done = true,
3460 "filter" => out.filter = true,
3461 "ref-in-want" => out.ref_in_want = true,
3462 "sideband-all" => out.sideband_all = true,
3463 "packfile-uris" => out.packfile_uris = true,
3464 other => out.unknown.push(other.to_string()),
3465 }
3466 }
3467 Ok(out)
3468}
3469
3470pub fn parse_capabilities(input: &[u8]) -> Result<Vec<Capability>> {
3471 let input = trim_trailing_lf(input);
3472 if input.is_empty() {
3473 return Ok(Vec::new());
3474 }
3475 let text =
3476 std::str::from_utf8(input).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3477 text.split(' ')
3478 .map(parse_capability_token)
3479 .collect::<Result<Vec<_>>>()
3480}
3481
3482pub fn encode_capabilities(capabilities: &[Capability]) -> Result<Vec<u8>> {
3483 let mut out = Vec::new();
3484 for (idx, capability) in capabilities.iter().enumerate() {
3485 validate_capability_field("capability name", &capability.name)?;
3486 if idx != 0 {
3487 out.push(b' ');
3488 }
3489 out.extend_from_slice(capability.name.as_bytes());
3490 if let Some(value) = &capability.value {
3491 validate_capability_field("capability value", value)?;
3492 out.push(b'=');
3493 out.extend_from_slice(value.as_bytes());
3494 }
3495 }
3496 Ok(out)
3497}
3498
3499pub fn parse_ref_advertisement(format: ObjectFormat, payload: &[u8]) -> Result<RefAdvertisement> {
3500 let payload = trim_trailing_lf(payload);
3501 let (reference, capabilities) = match payload.iter().position(|byte| *byte == 0) {
3502 Some(idx) => (&payload[..idx], parse_capabilities(&payload[idx + 1..])?),
3503 None => (payload, Vec::new()),
3504 };
3505 let text =
3506 std::str::from_utf8(reference).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3507 let (oid, name) = text
3508 .split_once(' ')
3509 .ok_or_else(|| GitError::InvalidFormat("advertised ref is missing name".into()))?;
3510 if name.is_empty() {
3511 return Err(GitError::InvalidFormat(
3512 "advertised ref name is empty".into(),
3513 ));
3514 }
3515 Ok(RefAdvertisement {
3516 oid: ObjectId::from_hex(format, oid)?,
3517 name: name.to_string(),
3518 capabilities,
3519 })
3520}
3521
3522pub fn encode_ref_advertisement(advertisement: &RefAdvertisement) -> Result<Vec<u8>> {
3523 validate_protocol_v2_token("advertised ref name", &advertisement.name)?;
3524 let mut out = advertisement.oid.to_string().into_bytes();
3525 out.push(b' ');
3526 out.extend_from_slice(advertisement.name.as_bytes());
3527 if !advertisement.capabilities.is_empty() {
3528 out.push(0);
3529 out.extend_from_slice(&encode_capabilities(&advertisement.capabilities)?);
3530 }
3531 out.push(b'\n');
3532 Ok(out)
3533}
3534
3535pub fn parse_ref_advertisements(
3536 format: ObjectFormat,
3537 frames: &[PktLineFrame],
3538) -> Result<Vec<RefAdvertisement>> {
3539 Ok(parse_ref_advertisement_set(format, frames)?.refs)
3540}
3541
3542pub fn parse_ref_advertisement_set(
3543 format: ObjectFormat,
3544 frames: &[PktLineFrame],
3545) -> Result<RefAdvertisementSet> {
3546 let mut set = RefAdvertisementSet {
3547 protocol: ProtocolVersion::V0,
3548 refs: Vec::new(),
3549 shallow: Vec::new(),
3550 };
3551 let mut saw_flush = false;
3552 let mut in_shallow = false;
3553 for (idx, frame) in frames.iter().enumerate() {
3554 match frame {
3555 PktLineFrame::Data(payload) if !saw_flush => {
3556 let trimmed = trim_trailing_lf(payload);
3557 if trimmed == b"version 1" {
3558 if idx != 0 {
3559 return Err(GitError::InvalidFormat(
3560 "advertised ref protocol version must be the first line".into(),
3561 ));
3562 }
3563 set.protocol = ProtocolVersion::V1;
3564 continue;
3565 }
3566 if trimmed.starts_with(b"version ") {
3567 return Err(GitError::InvalidFormat(
3568 "unsupported advertised ref protocol version".into(),
3569 ));
3570 }
3571 if trimmed.starts_with(b"shallow ") {
3572 if set.refs.is_empty() {
3573 return Err(GitError::InvalidFormat(
3574 "advertised shallow refs must follow advertised refs".into(),
3575 ));
3576 }
3577 let text = parse_protocol_v2_line_text("advertised shallow ref", payload)?;
3578 set.shallow.push(parse_oid_argument(
3579 format,
3580 "advertised shallow ref",
3581 text,
3582 "shallow ",
3583 )?);
3584 in_shallow = true;
3585 continue;
3586 }
3587 if in_shallow {
3588 return Err(GitError::InvalidFormat(
3589 "advertised refs must not follow shallow refs".into(),
3590 ));
3591 }
3592 let advertisement = parse_ref_advertisement(format, payload)?;
3593 if !set.refs.is_empty() && !advertisement.capabilities.is_empty() {
3594 return Err(GitError::InvalidFormat(
3595 "advertised ref capabilities must appear on the first ref".into(),
3596 ));
3597 }
3598 set.refs.push(advertisement);
3599 }
3600 PktLineFrame::Data(_) => {
3601 return Err(GitError::InvalidFormat(
3602 "advertised ref stream has data after flush".into(),
3603 ));
3604 }
3605 PktLineFrame::Flush => {
3606 saw_flush = true;
3607 if idx + 1 != frames.len() {
3608 return Err(GitError::InvalidFormat(
3609 "advertised ref stream has frames after flush".into(),
3610 ));
3611 }
3612 }
3613 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3614 return Err(GitError::InvalidFormat(
3615 "advertised ref stream contains a non-flush control packet".into(),
3616 ));
3617 }
3618 }
3619 }
3620 if !saw_flush {
3621 return Err(GitError::InvalidFormat(
3622 "advertised ref stream missing flush".into(),
3623 ));
3624 }
3625 Ok(set)
3626}
3627
3628pub fn encode_ref_advertisements(advertisements: &[RefAdvertisement]) -> Result<Vec<PktLineFrame>> {
3629 encode_ref_advertisement_set(&RefAdvertisementSet {
3630 protocol: ProtocolVersion::V0,
3631 refs: advertisements.to_vec(),
3632 shallow: Vec::new(),
3633 })
3634}
3635
3636pub fn encode_ref_advertisement_set(set: &RefAdvertisementSet) -> Result<Vec<PktLineFrame>> {
3637 let mut frames = Vec::new();
3638 match set.protocol {
3639 ProtocolVersion::V0 => {}
3640 ProtocolVersion::V1 => frames.push(PktLineFrame::data(line_from_str("version 1"))?),
3641 ProtocolVersion::V2 => {
3642 return Err(GitError::InvalidFormat(
3643 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3644 ));
3645 }
3646 }
3647 if set.refs.is_empty() && !set.shallow.is_empty() {
3648 return Err(GitError::InvalidFormat(
3649 "advertised shallow refs require advertised refs".into(),
3650 ));
3651 }
3652 for (idx, advertisement) in set.refs.iter().enumerate() {
3653 if idx != 0 && !advertisement.capabilities.is_empty() {
3654 return Err(GitError::InvalidFormat(
3655 "advertised ref capabilities must appear on the first ref".into(),
3656 ));
3657 }
3658 frames.push(PktLineFrame::data(encode_ref_advertisement(
3659 advertisement,
3660 )?)?);
3661 }
3662 for oid in &set.shallow {
3663 frames.push(PktLineFrame::data(line_from_str(&format!(
3664 "shallow {oid}"
3665 )))?);
3666 }
3667 frames.push(PktLineFrame::Flush);
3668 Ok(frames)
3669}
3670
3671pub fn read_ref_advertisements(
3672 format: ObjectFormat,
3673 reader: &mut impl Read,
3674) -> Result<Vec<RefAdvertisement>> {
3675 let frames = read_pkt_line_frames_until_flush(reader)?;
3676 parse_ref_advertisements(format, &frames)
3677}
3678
3679pub fn read_ref_advertisement_set(
3680 format: ObjectFormat,
3681 reader: &mut impl Read,
3682) -> Result<RefAdvertisementSet> {
3683 let frames = read_pkt_line_frames_until_flush(reader)?;
3684 parse_ref_advertisement_set(format, &frames)
3685}
3686
3687pub fn write_ref_advertisements(
3688 writer: &mut impl Write,
3689 advertisements: &[RefAdvertisement],
3690) -> Result<()> {
3691 write_ref_advertisement_stream(writer, ProtocolVersion::V0, advertisements, &[])
3692}
3693
3694pub fn write_ref_advertisement_set(
3695 writer: &mut impl Write,
3696 set: &RefAdvertisementSet,
3697) -> Result<()> {
3698 write_ref_advertisement_stream(writer, set.protocol, &set.refs, &set.shallow)
3699}
3700
3701fn write_ref_advertisement_stream(
3702 writer: &mut impl Write,
3703 protocol: ProtocolVersion,
3704 refs: &[RefAdvertisement],
3705 shallow: &[ObjectId],
3706) -> Result<()> {
3707 match protocol {
3708 ProtocolVersion::V0 => {}
3709 ProtocolVersion::V1 => write_pkt_line_payload(writer, b"version 1\n")?,
3710 ProtocolVersion::V2 => {
3711 return Err(GitError::InvalidFormat(
3712 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3713 ));
3714 }
3715 }
3716 if refs.is_empty() && !shallow.is_empty() {
3717 return Err(GitError::InvalidFormat(
3718 "advertised shallow refs require advertised refs".into(),
3719 ));
3720 }
3721 for (idx, advertisement) in refs.iter().enumerate() {
3722 if idx != 0 && !advertisement.capabilities.is_empty() {
3723 return Err(GitError::InvalidFormat(
3724 "advertised ref capabilities must appear on the first ref".into(),
3725 ));
3726 }
3727 write_pkt_line_payload(writer, &encode_ref_advertisement(advertisement)?)?;
3728 }
3729 for oid in shallow {
3730 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
3731 }
3732 writer.write_all(b"0000")?;
3733 Ok(())
3734}
3735
3736pub fn parse_dumb_http_info_refs(
3737 format: ObjectFormat,
3738 input: &[u8],
3739) -> Result<Vec<DumbHttpRefRecord>> {
3740 if input.is_empty() {
3741 return Ok(Vec::new());
3742 }
3743 input
3744 .split_inclusive(|byte| *byte == b'\n')
3745 .map(|line| parse_dumb_http_info_ref_record(format, line))
3746 .collect()
3747}
3748
3749pub fn encode_dumb_http_info_refs(records: &[DumbHttpRefRecord]) -> Result<Vec<u8>> {
3750 let mut out = Vec::new();
3751 for record in records {
3752 validate_dumb_http_ref_name(&record.name)?;
3753 out.extend_from_slice(record.oid.to_string().as_bytes());
3754 out.push(b'\t');
3755 out.extend_from_slice(record.name.as_bytes());
3756 if record.peeled {
3757 out.extend_from_slice(b"^{}");
3758 }
3759 out.push(b'\n');
3760 }
3761 Ok(out)
3762}
3763
3764pub fn read_dumb_http_info_refs(
3765 format: ObjectFormat,
3766 reader: &mut impl Read,
3767) -> Result<Vec<DumbHttpRefRecord>> {
3768 let mut input = Vec::new();
3769 reader.read_to_end(&mut input)?;
3770 parse_dumb_http_info_refs(format, &input)
3771}
3772
3773pub fn write_dumb_http_info_refs(
3774 writer: &mut impl Write,
3775 records: &[DumbHttpRefRecord],
3776) -> Result<()> {
3777 for record in records {
3778 validate_dumb_http_ref_name(&record.name)?;
3779 writer.write_all(record.oid.to_string().as_bytes())?;
3780 writer.write_all(b"\t")?;
3781 writer.write_all(record.name.as_bytes())?;
3782 if record.peeled {
3783 writer.write_all(b"^{}")?;
3784 }
3785 writer.write_all(b"\n")?;
3786 }
3787 Ok(())
3788}
3789
3790pub fn parse_dumb_http_alternates(input: &[u8]) -> Result<Vec<String>> {
3791 if input.is_empty() {
3792 return Ok(Vec::new());
3793 }
3794 input
3795 .split_inclusive(|byte| *byte == b'\n')
3796 .map(parse_dumb_http_alternate)
3797 .collect()
3798}
3799
3800pub fn encode_dumb_http_alternates(alternates: &[String]) -> Result<Vec<u8>> {
3801 let mut out = Vec::new();
3802 for alternate in alternates {
3803 validate_dumb_http_alternate(alternate)?;
3804 out.extend_from_slice(alternate.as_bytes());
3805 out.push(b'\n');
3806 }
3807 Ok(out)
3808}
3809
3810pub fn read_dumb_http_alternates(reader: &mut impl Read) -> Result<Vec<String>> {
3811 let mut input = Vec::new();
3812 reader.read_to_end(&mut input)?;
3813 parse_dumb_http_alternates(&input)
3814}
3815
3816pub fn write_dumb_http_alternates(writer: &mut impl Write, alternates: &[String]) -> Result<()> {
3817 for alternate in alternates {
3818 validate_dumb_http_alternate(alternate)?;
3819 writer.write_all(alternate.as_bytes())?;
3820 writer.write_all(b"\n")?;
3821 }
3822 Ok(())
3823}
3824
3825pub fn parse_dumb_http_packs(
3826 format: ObjectFormat,
3827 input: &[u8],
3828) -> Result<Vec<DumbHttpPackRecord>> {
3829 if input.is_empty() {
3830 return Ok(Vec::new());
3831 }
3832 input
3833 .split_inclusive(|byte| *byte == b'\n')
3834 .map(|line| parse_dumb_http_pack_record(format, line))
3835 .collect()
3836}
3837
3838pub fn encode_dumb_http_packs(records: &[DumbHttpPackRecord]) -> Result<Vec<u8>> {
3839 let mut out = Vec::new();
3840 for record in records {
3841 out.extend_from_slice(format!("P pack-{}.pack\n", record.hash).as_bytes());
3842 }
3843 Ok(out)
3844}
3845
3846pub fn read_dumb_http_packs(
3847 format: ObjectFormat,
3848 reader: &mut impl Read,
3849) -> Result<Vec<DumbHttpPackRecord>> {
3850 let mut input = Vec::new();
3851 reader.read_to_end(&mut input)?;
3852 parse_dumb_http_packs(format, &input)
3853}
3854
3855pub fn write_dumb_http_packs(
3856 writer: &mut impl Write,
3857 records: &[DumbHttpPackRecord],
3858) -> Result<()> {
3859 for record in records {
3860 writer.write_all(format!("P pack-{}.pack\n", record.hash).as_bytes())?;
3861 }
3862 Ok(())
3863}
3864
3865pub fn parse_upload_pack_request(
3866 format: ObjectFormat,
3867 frames: &[PktLineFrame],
3868) -> Result<Option<UploadPackRequest>> {
3869 if matches!(frames, [PktLineFrame::Flush]) {
3870 return Ok(None);
3871 }
3872
3873 let mut request = UploadPackRequest::default();
3874 let mut in_options = false;
3875 let mut saw_flush = false;
3876 for (idx, frame) in frames.iter().enumerate() {
3877 match frame {
3878 PktLineFrame::Data(payload) if !saw_flush => {
3879 let text = parse_protocol_v2_line_text("upload-pack request line", payload)?;
3880 if let Some(value) = text.strip_prefix("want ") {
3881 if in_options {
3882 return Err(GitError::InvalidFormat(
3883 "upload-pack request has want after options".into(),
3884 ));
3885 }
3886 let (oid, capabilities) = if request.wants.is_empty() {
3887 value
3888 .split_once(' ')
3889 .map_or((value, None), |(oid, caps)| (oid, Some(caps.as_bytes())))
3890 } else {
3891 if value.contains(' ') {
3892 return Err(GitError::InvalidFormat(
3893 "additional upload-pack want has capabilities".into(),
3894 ));
3895 }
3896 (value, None)
3897 };
3898 validate_protocol_v2_token("upload-pack want", oid)?;
3899 request.wants.push(ObjectId::from_hex(format, oid)?);
3900 if let Some(capabilities) = capabilities {
3901 request.capabilities = parse_capabilities(capabilities)?;
3902 }
3903 continue;
3904 }
3905
3906 if request.wants.is_empty() {
3907 return Err(GitError::InvalidFormat(
3908 "upload-pack request must start with want".into(),
3909 ));
3910 }
3911 in_options = true;
3912 if text.starts_with("shallow ") {
3913 request.shallow.push(parse_oid_argument(
3914 format,
3915 "upload-pack shallow",
3916 text,
3917 "shallow ",
3918 )?);
3919 } else if text.starts_with("deepen ") {
3920 if request.deepen.is_some() {
3921 return Err(GitError::InvalidFormat(
3922 "upload-pack request has duplicate deepen".into(),
3923 ));
3924 }
3925 request.deepen =
3926 Some(parse_u32_argument("upload-pack deepen", text, "deepen ")?);
3927 } else if text.starts_with("deepen-since ") {
3928 if request.deepen_since.is_some() {
3929 return Err(GitError::InvalidFormat(
3930 "upload-pack request has duplicate deepen-since".into(),
3931 ));
3932 }
3933 request.deepen_since = Some(parse_u64_argument(
3934 "upload-pack deepen-since",
3935 text,
3936 "deepen-since ",
3937 )?);
3938 } else if let Some(name) = text.strip_prefix("deepen-not ") {
3939 validate_protocol_v2_token("upload-pack deepen-not", name)?;
3940 request.deepen_not.push(name.to_string());
3941 } else if let Some(filter) = text.strip_prefix("filter ") {
3942 if request.filter.is_some() {
3943 return Err(GitError::InvalidFormat(
3944 "upload-pack request has duplicate filter".into(),
3945 ));
3946 }
3947 validate_protocol_v2_token("upload-pack filter", filter)?;
3948 request.filter = Some(filter.to_string());
3949 } else {
3950 return Err(GitError::InvalidFormat(format!(
3951 "unsupported upload-pack request line {text}"
3952 )));
3953 }
3954 }
3955 PktLineFrame::Data(_) => {
3956 return Err(GitError::InvalidFormat(
3957 "upload-pack request has data after flush".into(),
3958 ));
3959 }
3960 PktLineFrame::Flush => {
3961 saw_flush = true;
3962 if idx + 1 != frames.len() {
3963 return Err(GitError::InvalidFormat(
3964 "upload-pack request has frames after flush".into(),
3965 ));
3966 }
3967 }
3968 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3969 return Err(GitError::InvalidFormat(
3970 "upload-pack request contains a non-flush control packet".into(),
3971 ));
3972 }
3973 }
3974 }
3975 if !saw_flush {
3976 return Err(GitError::InvalidFormat(
3977 "upload-pack request missing flush".into(),
3978 ));
3979 }
3980 if request.wants.is_empty() {
3981 return Err(GitError::InvalidFormat(
3982 "upload-pack request missing want".into(),
3983 ));
3984 }
3985 Ok(Some(request))
3986}
3987
3988pub fn encode_upload_pack_request(
3989 request: Option<&UploadPackRequest>,
3990) -> Result<Vec<PktLineFrame>> {
3991 let Some(request) = request else {
3992 return Ok(vec![PktLineFrame::Flush]);
3993 };
3994 if request.wants.is_empty() {
3995 return Err(GitError::InvalidFormat(
3996 "upload-pack request missing want".into(),
3997 ));
3998 }
3999
4000 let mut frames = Vec::new();
4001 for (idx, oid) in request.wants.iter().enumerate() {
4002 let mut line = format!("want {oid}");
4003 if idx == 0 && !request.capabilities.is_empty() {
4004 line.push(' ');
4005 line.push_str(
4006 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4007 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4008 );
4009 }
4010 frames.push(PktLineFrame::data(line_from_str(&line))?);
4011 }
4012 for oid in &request.shallow {
4013 frames.push(PktLineFrame::data(line_from_str(&format!(
4014 "shallow {oid}"
4015 )))?);
4016 }
4017 if let Some(deepen) = request.deepen {
4018 if deepen == 0 {
4019 return Err(GitError::InvalidFormat(
4020 "upload-pack deepen must be positive".into(),
4021 ));
4022 }
4023 frames.push(PktLineFrame::data(line_from_str(&format!(
4024 "deepen {deepen}"
4025 )))?);
4026 }
4027 if let Some(deepen_since) = request.deepen_since {
4028 frames.push(PktLineFrame::data(line_from_str(&format!(
4029 "deepen-since {deepen_since}"
4030 )))?);
4031 }
4032 for name in &request.deepen_not {
4033 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4034 frames.push(PktLineFrame::data(line_from_str(&format!(
4035 "deepen-not {name}"
4036 )))?);
4037 }
4038 if let Some(filter) = &request.filter {
4039 validate_protocol_v2_token("upload-pack filter", filter)?;
4040 frames.push(PktLineFrame::data(line_from_str(&format!(
4041 "filter {filter}"
4042 )))?);
4043 }
4044 frames.push(PktLineFrame::Flush);
4045 Ok(frames)
4046}
4047
4048pub fn read_upload_pack_request(
4049 format: ObjectFormat,
4050 reader: &mut impl Read,
4051) -> Result<Option<UploadPackRequest>> {
4052 let frames = read_pkt_line_frames_until_flush(reader)?;
4053 parse_upload_pack_request(format, &frames)
4054}
4055
4056pub fn write_upload_pack_request(
4057 writer: &mut impl Write,
4058 request: Option<&UploadPackRequest>,
4059) -> Result<()> {
4060 let Some(request) = request else {
4061 writer.write_all(b"0000")?;
4062 return Ok(());
4063 };
4064 if request.wants.is_empty() {
4065 return Err(GitError::InvalidFormat(
4066 "upload-pack request missing want".into(),
4067 ));
4068 }
4069
4070 for (idx, oid) in request.wants.iter().enumerate() {
4071 let mut line = format!("want {oid}");
4072 if idx == 0 && !request.capabilities.is_empty() {
4073 line.push(' ');
4074 line.push_str(
4075 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4076 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4077 );
4078 }
4079 write_pkt_line_payload(writer, &line_from_str(&line))?;
4080 }
4081 for oid in &request.shallow {
4082 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
4083 }
4084 if let Some(deepen) = request.deepen {
4085 if deepen == 0 {
4086 return Err(GitError::InvalidFormat(
4087 "upload-pack deepen must be positive".into(),
4088 ));
4089 }
4090 write_pkt_line_payload(writer, &line_from_str(&format!("deepen {deepen}")))?;
4091 }
4092 if let Some(deepen_since) = request.deepen_since {
4093 write_pkt_line_payload(
4094 writer,
4095 &line_from_str(&format!("deepen-since {deepen_since}")),
4096 )?;
4097 }
4098 for name in &request.deepen_not {
4099 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4100 write_pkt_line_payload(writer, &line_from_str(&format!("deepen-not {name}")))?;
4101 }
4102 if let Some(filter) = &request.filter {
4103 validate_protocol_v2_token("upload-pack filter", filter)?;
4104 write_pkt_line_payload(writer, &line_from_str(&format!("filter {filter}")))?;
4105 }
4106 writer.write_all(b"0000")?;
4107 Ok(())
4108}
4109
4110pub fn parse_upload_pack_features(capabilities: &[Capability]) -> Result<UploadPackFeatures> {
4111 let mut features = UploadPackFeatures::default();
4112 for capability in capabilities {
4113 match capability.name.as_str() {
4114 "multi_ack" => set_upload_pack_flag(&mut features.multi_ack, capability)?,
4115 "multi_ack_detailed" => {
4116 set_upload_pack_flag(&mut features.multi_ack_detailed, capability)?
4117 }
4118 "no-done" => set_upload_pack_flag(&mut features.no_done, capability)?,
4119 "thin-pack" => set_upload_pack_flag(&mut features.thin_pack, capability)?,
4120 "side-band" => set_upload_pack_flag(&mut features.side_band, capability)?,
4121 "side-band-64k" => set_upload_pack_flag(&mut features.side_band_64k, capability)?,
4122 "ofs-delta" => set_upload_pack_flag(&mut features.ofs_delta, capability)?,
4123 "shallow" => set_upload_pack_flag(&mut features.shallow, capability)?,
4124 "deepen-since" => set_upload_pack_flag(&mut features.deepen_since, capability)?,
4125 "deepen-not" => set_upload_pack_flag(&mut features.deepen_not, capability)?,
4126 "include-tag" => set_upload_pack_flag(&mut features.include_tag, capability)?,
4127 "no-progress" => set_upload_pack_flag(&mut features.no_progress, capability)?,
4128 "allow-tip-sha1-in-want" => {
4129 set_upload_pack_flag(&mut features.allow_tip_sha1_in_want, capability)?
4130 }
4131 "allow-reachable-sha1-in-want" => {
4132 set_upload_pack_flag(&mut features.allow_reachable_sha1_in_want, capability)?
4133 }
4134 "filter" => set_upload_pack_flag(&mut features.filter, capability)?,
4135 "agent" => {
4136 let Some(agent) = &capability.value else {
4137 return Err(GitError::InvalidFormat(
4138 "upload-pack agent capability is missing value".into(),
4139 ));
4140 };
4141 if features.agent.is_some() {
4142 return Err(GitError::InvalidFormat(
4143 "upload-pack has duplicate agent capability".into(),
4144 ));
4145 }
4146 validate_capability_field("upload-pack agent", agent)?;
4147 features.agent = Some(agent.clone());
4148 }
4149 "object-format" => {
4150 let Some(format) = &capability.value else {
4151 return Err(GitError::InvalidFormat(
4152 "upload-pack object-format capability is missing value".into(),
4153 ));
4154 };
4155 if features.object_format.is_some() {
4156 return Err(GitError::InvalidFormat(
4157 "upload-pack has duplicate object-format capability".into(),
4158 ));
4159 }
4160 validate_capability_field("upload-pack object-format", format)?;
4161 features.object_format = Some(format.parse()?);
4162 }
4163 "symref" => {
4164 let Some(symref) = &capability.value else {
4165 return Err(GitError::InvalidFormat(
4166 "upload-pack symref capability is missing value".into(),
4167 ));
4168 };
4169 validate_capability_field("upload-pack symref", symref)?;
4170 features.symrefs.push(symref.clone());
4171 }
4172 _ => {
4173 encode_capabilities(std::slice::from_ref(capability))?;
4174 if features
4175 .unknown
4176 .iter()
4177 .any(|known| known.name == capability.name)
4178 {
4179 return Err(GitError::InvalidFormat(format!(
4180 "upload-pack has duplicate {} capability",
4181 capability.name
4182 )));
4183 }
4184 features.unknown.push(capability.clone());
4185 }
4186 }
4187 }
4188 Ok(features)
4189}
4190
4191pub fn encode_upload_pack_features(features: &UploadPackFeatures) -> Result<Vec<Capability>> {
4192 let mut capabilities = Vec::new();
4193 push_upload_pack_flag(&mut capabilities, "multi_ack", features.multi_ack);
4194 push_upload_pack_flag(
4195 &mut capabilities,
4196 "multi_ack_detailed",
4197 features.multi_ack_detailed,
4198 );
4199 push_upload_pack_flag(&mut capabilities, "no-done", features.no_done);
4200 push_upload_pack_flag(&mut capabilities, "thin-pack", features.thin_pack);
4201 push_upload_pack_flag(&mut capabilities, "side-band", features.side_band);
4202 push_upload_pack_flag(&mut capabilities, "side-band-64k", features.side_band_64k);
4203 push_upload_pack_flag(&mut capabilities, "ofs-delta", features.ofs_delta);
4204 push_upload_pack_flag(&mut capabilities, "shallow", features.shallow);
4205 push_upload_pack_flag(&mut capabilities, "deepen-since", features.deepen_since);
4206 push_upload_pack_flag(&mut capabilities, "deepen-not", features.deepen_not);
4207 push_upload_pack_flag(&mut capabilities, "include-tag", features.include_tag);
4208 push_upload_pack_flag(&mut capabilities, "no-progress", features.no_progress);
4209 push_upload_pack_flag(
4210 &mut capabilities,
4211 "allow-tip-sha1-in-want",
4212 features.allow_tip_sha1_in_want,
4213 );
4214 push_upload_pack_flag(
4215 &mut capabilities,
4216 "allow-reachable-sha1-in-want",
4217 features.allow_reachable_sha1_in_want,
4218 );
4219 push_upload_pack_flag(&mut capabilities, "filter", features.filter);
4220 if let Some(agent) = &features.agent {
4221 validate_capability_field("upload-pack agent", agent)?;
4222 capabilities.push(Capability {
4223 name: "agent".into(),
4224 value: Some(agent.clone()),
4225 });
4226 }
4227 if let Some(format) = features.object_format {
4228 capabilities.push(Capability {
4229 name: "object-format".into(),
4230 value: Some(format.name().into()),
4231 });
4232 }
4233 for symref in &features.symrefs {
4234 validate_capability_field("upload-pack symref", symref)?;
4235 capabilities.push(Capability {
4236 name: "symref".into(),
4237 value: Some(symref.clone()),
4238 });
4239 }
4240 for capability in &features.unknown {
4241 if is_known_upload_pack_capability(&capability.name) {
4242 return Err(GitError::InvalidFormat(format!(
4243 "upload-pack unknown capability duplicates known capability {}",
4244 capability.name
4245 )));
4246 }
4247 encode_capabilities(std::slice::from_ref(capability))?;
4248 capabilities.push(capability.clone());
4249 }
4250 Ok(capabilities)
4251}
4252
4253pub fn validate_upload_pack_request_features(
4254 features: &UploadPackFeatures,
4255 request: &UploadPackRequest,
4256) -> Result<()> {
4257 for capability in &request.capabilities {
4258 if is_upload_pack_flag_capability(&capability.name) {
4259 reject_capability_value("upload-pack request capability", capability)?;
4260 }
4261 match capability.name.as_str() {
4262 "multi_ack" if !features.multi_ack => {
4263 return Err(GitError::InvalidFormat(
4264 "upload-pack request uses multi_ack without advertised capability".into(),
4265 ));
4266 }
4267 "multi_ack_detailed" if !features.multi_ack_detailed => {
4268 return Err(GitError::InvalidFormat(
4269 "upload-pack request uses multi_ack_detailed without advertised capability"
4270 .into(),
4271 ));
4272 }
4273 "no-done" if !features.no_done => {
4274 return Err(GitError::InvalidFormat(
4275 "upload-pack request uses no-done without advertised capability".into(),
4276 ));
4277 }
4278 "thin-pack" if !features.thin_pack => {
4279 return Err(GitError::InvalidFormat(
4280 "upload-pack request uses thin-pack without advertised capability".into(),
4281 ));
4282 }
4283 "side-band" if !features.side_band => {
4284 return Err(GitError::InvalidFormat(
4285 "upload-pack request uses side-band without advertised capability".into(),
4286 ));
4287 }
4288 "side-band-64k" if !features.side_band_64k => {
4289 return Err(GitError::InvalidFormat(
4290 "upload-pack request uses side-band-64k without advertised capability".into(),
4291 ));
4292 }
4293 "ofs-delta" if !features.ofs_delta => {
4294 return Err(GitError::InvalidFormat(
4295 "upload-pack request uses ofs-delta without advertised capability".into(),
4296 ));
4297 }
4298 "include-tag" if !features.include_tag => {
4299 return Err(GitError::InvalidFormat(
4300 "upload-pack request uses include-tag without advertised capability".into(),
4301 ));
4302 }
4303 "no-progress" if !features.no_progress => {
4304 return Err(GitError::InvalidFormat(
4305 "upload-pack request uses no-progress without advertised capability".into(),
4306 ));
4307 }
4308 "allow-tip-sha1-in-want" if !features.allow_tip_sha1_in_want => {
4309 return Err(GitError::InvalidFormat(
4310 "upload-pack request uses allow-tip-sha1-in-want without advertised capability"
4311 .into(),
4312 ));
4313 }
4314 "allow-reachable-sha1-in-want" if !features.allow_reachable_sha1_in_want => {
4315 return Err(GitError::InvalidFormat(
4316 "upload-pack request uses allow-reachable-sha1-in-want without advertised capability"
4317 .into(),
4318 ));
4319 }
4320 "filter" if !features.filter => {
4321 return Err(GitError::InvalidFormat(
4322 "upload-pack request uses filter capability without advertised capability"
4323 .into(),
4324 ));
4325 }
4326 "agent" => {
4327 let Some(agent) = &capability.value else {
4328 return Err(GitError::InvalidFormat(
4329 "upload-pack request agent capability is missing value".into(),
4330 ));
4331 };
4332 validate_capability_field("upload-pack request agent", agent)?;
4333 }
4334 "object-format" => {
4335 let Some(format) = &capability.value else {
4336 return Err(GitError::InvalidFormat(
4337 "upload-pack request object-format capability is missing value".into(),
4338 ));
4339 };
4340 let requested_format: ObjectFormat = format.parse()?;
4341 if features.object_format != Some(requested_format) {
4342 return Err(GitError::InvalidFormat(
4343 "upload-pack request object-format was not advertised".into(),
4344 ));
4345 }
4346 }
4347 name if is_known_upload_pack_capability(name) => {}
4348 _ => {
4349 if !features
4350 .unknown
4351 .iter()
4352 .any(|advertised| advertised.name == capability.name)
4353 {
4354 return Err(GitError::InvalidFormat(format!(
4355 "upload-pack request uses unadvertised capability {}",
4356 capability.name
4357 )));
4358 }
4359 }
4360 }
4361 }
4362
4363 let sideband = request
4364 .capabilities
4365 .iter()
4366 .any(|capability| capability.name == "side-band");
4367 let sideband_64k = request
4368 .capabilities
4369 .iter()
4370 .any(|capability| capability.name == "side-band-64k");
4371 if sideband && sideband_64k {
4372 return Err(GitError::InvalidFormat(
4373 "upload-pack request must not request both side-band and side-band-64k".into(),
4374 ));
4375 }
4376
4377 if !features.shallow && (!request.shallow.is_empty() || request.deepen.is_some()) {
4378 return Err(GitError::InvalidFormat(
4379 "upload-pack request uses shallow/deepen without advertised shallow capability".into(),
4380 ));
4381 }
4382 if !features.deepen_since && request.deepen_since.is_some() {
4383 return Err(GitError::InvalidFormat(
4384 "upload-pack request uses deepen-since without advertised capability".into(),
4385 ));
4386 }
4387 if !features.deepen_not && !request.deepen_not.is_empty() {
4388 return Err(GitError::InvalidFormat(
4389 "upload-pack request uses deepen-not without advertised capability".into(),
4390 ));
4391 }
4392 if !features.filter && request.filter.is_some() {
4393 return Err(GitError::InvalidFormat(
4394 "upload-pack request uses filter without advertised capability".into(),
4395 ));
4396 }
4397 Ok(())
4398}
4399
4400pub fn build_upload_pack_raw_packfile_response<C, B>(
4401 features: &UploadPackFeatures,
4402 request: UploadPackRequest,
4403 haves: impl IntoIterator<Item = ObjectId>,
4404 mut contains_object: C,
4405 mut build_pack: B,
4406) -> Result<UploadPackRawPackfileResponse>
4407where
4408 C: FnMut(&ObjectId) -> Result<bool>,
4409 B: FnMut(Vec<ObjectId>, Vec<ObjectId>) -> Result<Option<Vec<u8>>>,
4410{
4411 validate_upload_pack_request_features(features, &request)?;
4412 for want in &request.wants {
4413 if !contains_object(want)? {
4414 return Err(GitError::InvalidObject(format!(
4415 "upload-pack requested missing object {want}"
4416 )));
4417 }
4418 }
4419 let known_haves = haves
4420 .into_iter()
4421 .filter_map(|oid| match contains_object(&oid) {
4422 Ok(true) => Some(Ok(oid)),
4423 Ok(false) => None,
4424 Err(err) => Some(Err(err)),
4425 })
4426 .collect::<Result<Vec<_>>>()?;
4427 let packfile = build_pack(request.wants, known_haves)?
4428 .ok_or_else(|| GitError::InvalidObject("upload-pack request produced empty pack".into()))?;
4429 Ok(UploadPackRawPackfileResponse {
4430 acknowledgments: vec![UploadPackAcknowledgment::Nak],
4431 packfile,
4432 })
4433}
4434
4435pub fn parse_upload_pack_shallow_update(
4436 format: ObjectFormat,
4437 frames: &[PktLineFrame],
4438) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4439 let mut entries = Vec::new();
4440 let mut saw_flush = false;
4441 for (idx, frame) in frames.iter().enumerate() {
4442 match frame {
4443 PktLineFrame::Data(payload) if !saw_flush => {
4444 entries.push(parse_fetch_shallow_info(format, payload)?);
4445 }
4446 PktLineFrame::Data(_) => {
4447 return Err(GitError::InvalidFormat(
4448 "upload-pack shallow update has data after flush".into(),
4449 ));
4450 }
4451 PktLineFrame::Flush => {
4452 saw_flush = true;
4453 if idx + 1 != frames.len() {
4454 return Err(GitError::InvalidFormat(
4455 "upload-pack shallow update has frames after flush".into(),
4456 ));
4457 }
4458 }
4459 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4460 return Err(GitError::InvalidFormat(
4461 "upload-pack shallow update contains a non-flush control packet".into(),
4462 ));
4463 }
4464 }
4465 }
4466 if !saw_flush {
4467 return Err(GitError::InvalidFormat(
4468 "upload-pack shallow update missing flush".into(),
4469 ));
4470 }
4471 Ok(entries)
4472}
4473
4474pub fn encode_upload_pack_shallow_update(
4475 entries: &[ProtocolV2FetchShallowInfo],
4476) -> Result<Vec<PktLineFrame>> {
4477 let mut frames = Vec::new();
4478 for entry in entries {
4479 let line = match entry {
4480 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4481 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4482 };
4483 frames.push(PktLineFrame::data(line_from_str(&line))?);
4484 }
4485 frames.push(PktLineFrame::Flush);
4486 Ok(frames)
4487}
4488
4489pub fn read_upload_pack_shallow_update(
4490 format: ObjectFormat,
4491 reader: &mut impl Read,
4492) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4493 let frames = read_pkt_line_frames_until_flush(reader)?;
4494 parse_upload_pack_shallow_update(format, &frames)
4495}
4496
4497pub fn write_upload_pack_shallow_update(
4498 writer: &mut impl Write,
4499 entries: &[ProtocolV2FetchShallowInfo],
4500) -> Result<()> {
4501 for entry in entries {
4502 let line = match entry {
4503 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4504 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4505 };
4506 write_pkt_line_payload(writer, &line_from_str(&line))?;
4507 }
4508 writer.write_all(b"0000")?;
4509 Ok(())
4510}
4511
4512pub fn parse_upload_pack_negotiation_request(
4513 format: ObjectFormat,
4514 frames: &[PktLineFrame],
4515) -> Result<UploadPackNegotiationRequest> {
4516 let mut request = UploadPackNegotiationRequest::default();
4517 let mut terminated = false;
4518 for (idx, frame) in frames.iter().enumerate() {
4519 match frame {
4520 PktLineFrame::Data(payload) if !terminated => {
4521 let text = parse_protocol_v2_line_text("upload-pack negotiation line", payload)?;
4522 if text == "done" {
4523 request.done = true;
4524 terminated = true;
4525 if idx + 1 != frames.len() {
4526 return Err(GitError::InvalidFormat(
4527 "upload-pack negotiation has frames after done".into(),
4528 ));
4529 }
4530 } else if text.starts_with("have ") {
4531 request.haves.push(parse_oid_argument(
4532 format,
4533 "upload-pack have",
4534 text,
4535 "have ",
4536 )?);
4537 } else {
4538 return Err(GitError::InvalidFormat(format!(
4539 "unsupported upload-pack negotiation line {text}"
4540 )));
4541 }
4542 }
4543 PktLineFrame::Data(_) => {
4544 return Err(GitError::InvalidFormat(
4545 "upload-pack negotiation has data after terminator".into(),
4546 ));
4547 }
4548 PktLineFrame::Flush => {
4549 terminated = true;
4550 if idx + 1 != frames.len() {
4551 return Err(GitError::InvalidFormat(
4552 "upload-pack negotiation has frames after flush".into(),
4553 ));
4554 }
4555 }
4556 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4557 return Err(GitError::InvalidFormat(
4558 "upload-pack negotiation contains a non-flush control packet".into(),
4559 ));
4560 }
4561 }
4562 }
4563 if !terminated {
4564 return Err(GitError::InvalidFormat(
4565 "upload-pack negotiation missing terminator".into(),
4566 ));
4567 }
4568 Ok(request)
4569}
4570
4571pub fn encode_upload_pack_negotiation_request(
4572 request: &UploadPackNegotiationRequest,
4573) -> Result<Vec<PktLineFrame>> {
4574 let mut frames = Vec::new();
4575 for oid in &request.haves {
4576 frames.push(PktLineFrame::data(line_from_str(&format!("have {oid}")))?);
4577 }
4578 if request.done {
4579 frames.push(PktLineFrame::data(line_from_str("done"))?);
4580 } else {
4581 frames.push(PktLineFrame::Flush);
4582 }
4583 Ok(frames)
4584}
4585
4586pub fn read_upload_pack_negotiation_request(
4587 format: ObjectFormat,
4588 reader: &mut impl Read,
4589) -> Result<UploadPackNegotiationRequest> {
4590 let mut frames = Vec::new();
4591 loop {
4592 let Some(frame) = read_pkt_line_frame(reader)? else {
4593 return Err(GitError::InvalidFormat(
4594 "pkt-line stream ended before upload-pack negotiation terminator".into(),
4595 ));
4596 };
4597 let done = match &frame {
4598 PktLineFrame::Flush => true,
4599 PktLineFrame::Data(payload) => trim_trailing_lf(payload) == b"done",
4600 _ => false,
4601 };
4602 frames.push(frame);
4603 if done {
4604 return parse_upload_pack_negotiation_request(format, &frames);
4605 }
4606 }
4607}
4608
4609pub fn write_upload_pack_negotiation_request(
4610 writer: &mut impl Write,
4611 request: &UploadPackNegotiationRequest,
4612) -> Result<()> {
4613 for oid in &request.haves {
4614 write_pkt_line_payload(writer, &line_from_str(&format!("have {oid}")))?;
4615 }
4616 if request.done {
4617 write_pkt_line_payload(writer, b"done\n")?;
4618 } else {
4619 writer.write_all(b"0000")?;
4620 }
4621 Ok(())
4622}
4623
4624pub fn parse_upload_pack_acknowledgment(
4625 format: ObjectFormat,
4626 payload: &[u8],
4627) -> Result<UploadPackAcknowledgment> {
4628 let text = parse_protocol_v2_line_text("upload-pack acknowledgment", payload)?;
4629 if text == "NAK" {
4630 return Ok(UploadPackAcknowledgment::Nak);
4631 }
4632 let Some(rest) = text.strip_prefix("ACK ") else {
4633 return Err(GitError::InvalidFormat(format!(
4634 "unsupported upload-pack acknowledgment {text}"
4635 )));
4636 };
4637 let mut fields = rest.split(' ');
4638 let oid = fields
4639 .next()
4640 .ok_or_else(|| GitError::InvalidFormat("upload-pack ACK missing object id".into()))?;
4641 validate_protocol_v2_token("upload-pack ACK", oid)?;
4642 let status = match fields.next() {
4643 None => None,
4644 Some("continue") => Some(UploadPackAckStatus::Continue),
4645 Some("common") => Some(UploadPackAckStatus::Common),
4646 Some("ready") => Some(UploadPackAckStatus::Ready),
4647 Some(other) => {
4648 return Err(GitError::InvalidFormat(format!(
4649 "unsupported upload-pack ACK status {other}"
4650 )));
4651 }
4652 };
4653 if fields.next().is_some() {
4654 return Err(GitError::InvalidFormat(
4655 "upload-pack ACK has too many fields".into(),
4656 ));
4657 }
4658 Ok(UploadPackAcknowledgment::Ack {
4659 oid: ObjectId::from_hex(format, oid)?,
4660 status,
4661 })
4662}
4663
4664pub fn encode_upload_pack_acknowledgment(
4665 acknowledgment: &UploadPackAcknowledgment,
4666) -> Result<Vec<u8>> {
4667 let line = match acknowledgment {
4668 UploadPackAcknowledgment::Nak => "NAK".to_string(),
4669 UploadPackAcknowledgment::Ack { oid, status } => {
4670 let mut line = format!("ACK {oid}");
4671 if let Some(status) = status {
4672 line.push(' ');
4673 line.push_str(match status {
4674 UploadPackAckStatus::Continue => "continue",
4675 UploadPackAckStatus::Common => "common",
4676 UploadPackAckStatus::Ready => "ready",
4677 });
4678 }
4679 line
4680 }
4681 };
4682 Ok(line_from_str(&line))
4683}
4684
4685pub fn read_upload_pack_acknowledgment(
4686 format: ObjectFormat,
4687 reader: &mut impl Read,
4688) -> Result<UploadPackAcknowledgment> {
4689 let Some(frame) = read_pkt_line_frame(reader)? else {
4690 return Err(GitError::InvalidFormat(
4691 "pkt-line stream ended before upload-pack acknowledgment".into(),
4692 ));
4693 };
4694 match frame {
4695 PktLineFrame::Data(payload) => parse_upload_pack_acknowledgment(format, &payload),
4696 _ => Err(GitError::InvalidFormat(
4697 "upload-pack acknowledgment must be a data packet".into(),
4698 )),
4699 }
4700}
4701
4702pub fn write_upload_pack_acknowledgment(
4703 writer: &mut impl Write,
4704 acknowledgment: &UploadPackAcknowledgment,
4705) -> Result<()> {
4706 write_pkt_line_payload(writer, &encode_upload_pack_acknowledgment(acknowledgment)?)
4707}
4708
4709pub fn parse_upload_pack_packfile_response(
4710 format: ObjectFormat,
4711 frames: &[PktLineFrame],
4712) -> Result<UploadPackPackfileResponse> {
4713 let mut response = UploadPackPackfileResponse::default();
4714 let mut in_sideband = false;
4715 let mut saw_flush = false;
4716 for (idx, frame) in frames.iter().enumerate() {
4717 match frame {
4718 PktLineFrame::Data(payload) if !saw_flush => {
4719 if !in_sideband
4720 && (trim_trailing_lf(payload) == b"NAK" || payload.starts_with(b"ACK "))
4721 {
4722 response
4723 .acknowledgments
4724 .push(parse_upload_pack_acknowledgment(format, payload)?);
4725 continue;
4726 }
4727 in_sideband = true;
4728 response.sideband.push(parse_sideband_packet(payload)?);
4729 }
4730 PktLineFrame::Data(_) => {
4731 return Err(GitError::InvalidFormat(
4732 "upload-pack packfile response has data after flush".into(),
4733 ));
4734 }
4735 PktLineFrame::Flush => {
4736 saw_flush = true;
4737 if idx + 1 != frames.len() {
4738 return Err(GitError::InvalidFormat(
4739 "upload-pack packfile response has frames after flush".into(),
4740 ));
4741 }
4742 }
4743 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4744 return Err(GitError::InvalidFormat(
4745 "upload-pack packfile response contains a non-flush control packet".into(),
4746 ));
4747 }
4748 }
4749 }
4750 if !saw_flush {
4751 return Err(GitError::InvalidFormat(
4752 "upload-pack packfile response missing flush".into(),
4753 ));
4754 }
4755 Ok(response)
4756}
4757
4758pub fn encode_upload_pack_packfile_response(
4759 response: &UploadPackPackfileResponse,
4760) -> Result<Vec<PktLineFrame>> {
4761 let mut frames = Vec::new();
4762 for acknowledgment in &response.acknowledgments {
4763 frames.push(PktLineFrame::data(encode_upload_pack_acknowledgment(
4764 acknowledgment,
4765 )?)?);
4766 }
4767 for packet in &response.sideband {
4768 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
4769 }
4770 frames.push(PktLineFrame::Flush);
4771 Ok(frames)
4772}
4773
4774pub fn read_upload_pack_packfile_response(
4775 format: ObjectFormat,
4776 reader: &mut impl Read,
4777) -> Result<UploadPackPackfileResponse> {
4778 let frames = read_pkt_line_frames_until_flush(reader)?;
4779 parse_upload_pack_packfile_response(format, &frames)
4780}
4781
4782pub fn write_upload_pack_packfile_response(
4783 writer: &mut impl Write,
4784 response: &UploadPackPackfileResponse,
4785) -> Result<()> {
4786 for acknowledgment in &response.acknowledgments {
4787 write_upload_pack_acknowledgment(writer, acknowledgment)?;
4788 }
4789 for packet in &response.sideband {
4790 write_sideband_packet(writer, packet)?;
4791 }
4792 writer.write_all(b"0000")?;
4793 Ok(())
4794}
4795
4796pub fn demux_upload_pack_packfile_response(
4797 response: &UploadPackPackfileResponse,
4798) -> Result<SideBandDemux> {
4799 demux_sideband_packets(&response.sideband)
4800}
4801
4802pub fn parse_upload_pack_raw_packfile_response(
4803 format: ObjectFormat,
4804 input: &[u8],
4805) -> Result<UploadPackRawPackfileResponse> {
4806 let mut response = UploadPackRawPackfileResponse::default();
4807 let mut offset = 0usize;
4808 while offset < input.len() {
4809 match PktLineFrame::parse(&input[offset..]) {
4810 Ok((PktLineFrame::Data(payload), consumed)) => {
4811 let trimmed = trim_trailing_lf(&payload);
4812 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
4813 response
4814 .acknowledgments
4815 .push(parse_upload_pack_acknowledgment(format, &payload)?);
4816 offset += consumed;
4817 continue;
4818 }
4819 return Err(GitError::InvalidFormat(
4820 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
4821 ));
4822 }
4823 Ok((PktLineFrame::Flush | PktLineFrame::Delimiter | PktLineFrame::ResponseEnd, _)) => {
4824 return Err(GitError::InvalidFormat(
4825 "upload-pack raw packfile response contains a control packet".into(),
4826 ));
4827 }
4828 Err(_) if input[offset..].starts_with(b"PACK") => break,
4829 Err(err) => return Err(err),
4830 }
4831 }
4832 response.packfile = input[offset..].to_vec();
4833 if response.packfile.is_empty() {
4834 return Err(GitError::InvalidFormat(
4835 "upload-pack raw packfile response missing packfile".into(),
4836 ));
4837 }
4838 if !response.packfile.starts_with(b"PACK") {
4839 return Err(GitError::InvalidFormat(
4840 "upload-pack raw packfile response packfile must start with PACK".into(),
4841 ));
4842 }
4843 Ok(response)
4844}
4845
4846pub fn encode_upload_pack_raw_packfile_response(
4847 response: &UploadPackRawPackfileResponse,
4848) -> Result<Vec<u8>> {
4849 if response.packfile.is_empty() {
4850 return Err(GitError::InvalidFormat(
4851 "upload-pack raw packfile response missing packfile".into(),
4852 ));
4853 }
4854 if !response.packfile.starts_with(b"PACK") {
4855 return Err(GitError::InvalidFormat(
4856 "upload-pack raw packfile response packfile must start with PACK".into(),
4857 ));
4858 }
4859 let mut out = Vec::new();
4860 for acknowledgment in &response.acknowledgments {
4861 write_pkt_line_payload(
4862 &mut out,
4863 &encode_upload_pack_acknowledgment(acknowledgment)?,
4864 )?;
4865 }
4866 out.extend_from_slice(&response.packfile);
4867 Ok(out)
4868}
4869
4870pub fn read_upload_pack_raw_packfile_response(
4871 format: ObjectFormat,
4872 reader: &mut impl Read,
4873) -> Result<UploadPackRawPackfileResponse> {
4874 let mut input = Vec::new();
4875 reader.read_to_end(&mut input)?;
4876 parse_upload_pack_raw_packfile_response(format, &input)
4877}
4878
4879pub fn write_upload_pack_raw_packfile_response(
4880 writer: &mut impl Write,
4881 response: &UploadPackRawPackfileResponse,
4882) -> Result<()> {
4883 if response.packfile.is_empty() {
4884 return Err(GitError::InvalidFormat(
4885 "upload-pack raw packfile response missing packfile".into(),
4886 ));
4887 }
4888 if !response.packfile.starts_with(b"PACK") {
4889 return Err(GitError::InvalidFormat(
4890 "upload-pack raw packfile response packfile must start with PACK".into(),
4891 ));
4892 }
4893 for acknowledgment in &response.acknowledgments {
4894 write_upload_pack_acknowledgment(writer, acknowledgment)?;
4895 }
4896 writer.write_all(&response.packfile)?;
4897 Ok(())
4898}
4899
4900pub fn parse_upload_pack_shallow_info_section(
4911 format: ObjectFormat,
4912 input: &[u8],
4913) -> Result<(Vec<ProtocolV2FetchShallowInfo>, usize)> {
4914 let mut entries = Vec::new();
4915 let mut offset = 0usize;
4916 loop {
4917 let (frame, consumed) = PktLineFrame::parse(&input[offset..])?;
4918 offset += consumed;
4919 match frame {
4920 PktLineFrame::Data(payload) => {
4921 entries.push(parse_fetch_shallow_info(format, &payload)?)
4922 }
4923 PktLineFrame::Flush => return Ok((entries, offset)),
4924 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4925 return Err(GitError::InvalidFormat(
4926 "upload-pack shallow-info section contains a non-flush control packet".into(),
4927 ));
4928 }
4929 }
4930 }
4931}
4932
4933pub fn parse_upload_pack_shallow_info_and_raw_packfile_response(
4944 format: ObjectFormat,
4945 input: &[u8],
4946) -> Result<(
4947 Vec<ProtocolV2FetchShallowInfo>,
4948 UploadPackRawPackfileResponse,
4949)> {
4950 let (shallow, consumed) = parse_upload_pack_shallow_info_section(format, input)?;
4951 let response = parse_upload_pack_raw_packfile_response(format, &input[consumed..])?;
4952 Ok((shallow, response))
4953}
4954
4955pub fn read_upload_pack_shallow_info_and_raw_packfile_response(
4963 format: ObjectFormat,
4964 reader: &mut impl Read,
4965) -> Result<(
4966 Vec<ProtocolV2FetchShallowInfo>,
4967 UploadPackRawPackfileResponse,
4968)> {
4969 let mut input = Vec::new();
4970 reader.read_to_end(&mut input)?;
4971 parse_upload_pack_shallow_info_and_raw_packfile_response(format, &input)
4972}
4973
4974pub fn parse_receive_pack_request(
4975 format: ObjectFormat,
4976 frames: &[PktLineFrame],
4977) -> Result<ReceivePackRequest> {
4978 let mut request = ReceivePackRequest::default();
4979 let mut saw_command = false;
4980 let mut saw_flush = false;
4981 for (idx, frame) in frames.iter().enumerate() {
4982 match frame {
4983 PktLineFrame::Data(payload) if !saw_flush => {
4984 let payload = trim_trailing_lf(payload);
4985 if payload.is_empty() {
4986 return Err(GitError::InvalidFormat(
4987 "receive-pack request line is empty".into(),
4988 ));
4989 }
4990 if let Some(shallow) = payload.strip_prefix(b"shallow ") {
4991 if saw_command {
4992 return Err(GitError::InvalidFormat(
4993 "receive-pack request has shallow after commands".into(),
4994 ));
4995 }
4996 let shallow = std::str::from_utf8(shallow)
4997 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
4998 validate_protocol_v2_token("receive-pack shallow", shallow)?;
4999 request.shallow.push(ObjectId::from_hex(format, shallow)?);
5000 continue;
5001 }
5002
5003 let (command, capabilities) = match payload.iter().position(|byte| *byte == 0) {
5004 Some(nul) if !saw_command => (
5005 &payload[..nul],
5006 Some(parse_capabilities(&payload[nul + 1..])?),
5007 ),
5008 Some(_) => {
5009 return Err(GitError::InvalidFormat(
5010 "receive-pack capabilities must appear on the first command".into(),
5011 ));
5012 }
5013 None => (payload, None),
5014 };
5015 let command = parse_receive_pack_command(format, command)?;
5016 if let Some(capabilities) = capabilities {
5017 request.capabilities = capabilities;
5018 }
5019 request.commands.push(command);
5020 saw_command = true;
5021 }
5022 PktLineFrame::Data(_) => {
5023 return Err(GitError::InvalidFormat(
5024 "receive-pack request has data after flush".into(),
5025 ));
5026 }
5027 PktLineFrame::Flush => {
5028 saw_flush = true;
5029 if idx + 1 != frames.len() {
5030 return Err(GitError::InvalidFormat(
5031 "receive-pack request has frames after flush".into(),
5032 ));
5033 }
5034 }
5035 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5036 return Err(GitError::InvalidFormat(
5037 "receive-pack request contains a non-flush control packet".into(),
5038 ));
5039 }
5040 }
5041 }
5042 if !saw_flush {
5043 return Err(GitError::InvalidFormat(
5044 "receive-pack request missing flush".into(),
5045 ));
5046 }
5047 if !request.shallow.is_empty() && request.commands.is_empty() {
5048 return Err(GitError::InvalidFormat(
5049 "receive-pack request has shallow lines without commands".into(),
5050 ));
5051 }
5052 Ok(request)
5053}
5054
5055pub fn encode_receive_pack_request(request: &ReceivePackRequest) -> Result<Vec<PktLineFrame>> {
5056 if !request.shallow.is_empty() && request.commands.is_empty() {
5057 return Err(GitError::InvalidFormat(
5058 "receive-pack request has shallow lines without commands".into(),
5059 ));
5060 }
5061
5062 let mut frames = Vec::new();
5063 for oid in &request.shallow {
5064 frames.push(PktLineFrame::data(line_from_str(&format!(
5065 "shallow {oid}"
5066 )))?);
5067 }
5068 for (idx, command) in request.commands.iter().enumerate() {
5069 let mut payload = format_receive_pack_command(command)?;
5070 if idx == 0 && !request.capabilities.is_empty() {
5071 payload.push(0);
5072 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5073 }
5074 payload.push(b'\n');
5075 frames.push(PktLineFrame::data(payload)?);
5076 }
5077 frames.push(PktLineFrame::Flush);
5078 Ok(frames)
5079}
5080
5081pub fn read_receive_pack_request(
5082 format: ObjectFormat,
5083 reader: &mut impl Read,
5084) -> Result<ReceivePackRequest> {
5085 let frames = read_pkt_line_frames_until_flush(reader)?;
5086 parse_receive_pack_request(format, &frames)
5087}
5088
5089pub fn write_receive_pack_request(
5090 writer: &mut impl Write,
5091 request: &ReceivePackRequest,
5092) -> Result<()> {
5093 if !request.shallow.is_empty() && request.commands.is_empty() {
5094 return Err(GitError::InvalidFormat(
5095 "receive-pack request has shallow lines without commands".into(),
5096 ));
5097 }
5098
5099 for oid in &request.shallow {
5100 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
5101 }
5102 for (idx, command) in request.commands.iter().enumerate() {
5103 let mut payload = format_receive_pack_command(command)?;
5104 if idx == 0 && !request.capabilities.is_empty() {
5105 payload.push(0);
5106 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5107 }
5108 payload.push(b'\n');
5109 write_pkt_line_payload(writer, &payload)?;
5110 }
5111 writer.write_all(b"0000")?;
5112 Ok(())
5113}
5114
5115pub fn parse_receive_pack_push_request(
5116 format: ObjectFormat,
5117 input: &[u8],
5118 has_push_options: bool,
5119) -> Result<ReceivePackPushRequest> {
5120 let (command_frames, consumed) = parse_pkt_line_frames_until_flush_from(input)?;
5121 let commands = parse_receive_pack_request(format, &command_frames)?;
5122 let mut offset = consumed;
5123 let push_options = if has_push_options {
5124 let (push_option_frames, consumed) =
5125 parse_pkt_line_frames_until_flush_from(&input[offset..])?;
5126 offset += consumed;
5127 Some(parse_receive_pack_push_options(&push_option_frames)?)
5128 } else {
5129 None
5130 };
5131 Ok(ReceivePackPushRequest {
5132 commands,
5133 push_options,
5134 packfile: input[offset..].to_vec(),
5135 })
5136}
5137
5138pub fn encode_receive_pack_push_request(request: &ReceivePackPushRequest) -> Result<Vec<u8>> {
5139 let mut out = Vec::new();
5140 write_receive_pack_request(&mut out, &request.commands)?;
5141 if let Some(push_options) = &request.push_options {
5142 write_receive_pack_push_options(&mut out, push_options)?;
5143 }
5144 out.extend_from_slice(&request.packfile);
5145 Ok(out)
5146}
5147
5148pub fn read_receive_pack_push_request(
5149 format: ObjectFormat,
5150 reader: &mut impl Read,
5151 has_push_options: bool,
5152) -> Result<ReceivePackPushRequest> {
5153 let commands = read_receive_pack_request(format, reader)?;
5154 let push_options = if has_push_options {
5155 Some(read_receive_pack_push_options(reader)?)
5156 } else {
5157 None
5158 };
5159 let mut packfile = Vec::new();
5160 reader.read_to_end(&mut packfile)?;
5161 Ok(ReceivePackPushRequest {
5162 commands,
5163 push_options,
5164 packfile,
5165 })
5166}
5167
5168pub fn write_receive_pack_push_request(
5169 writer: &mut impl Write,
5170 request: &ReceivePackPushRequest,
5171) -> Result<()> {
5172 write_receive_pack_request(writer, &request.commands)?;
5173 if let Some(push_options) = &request.push_options {
5174 write_receive_pack_push_options(writer, push_options)?;
5175 }
5176 writer.write_all(&request.packfile)?;
5177 Ok(())
5178}
5179
5180pub fn parse_receive_pack_features(capabilities: &[Capability]) -> Result<ReceivePackFeatures> {
5181 let mut features = ReceivePackFeatures::default();
5182 for capability in capabilities {
5183 match capability.name.as_str() {
5184 "report-status" => {
5185 reject_capability_value("receive-pack report-status", capability)?;
5186 if features.report_status {
5187 return Err(GitError::InvalidFormat(
5188 "receive-pack has duplicate report-status capability".into(),
5189 ));
5190 }
5191 features.report_status = true;
5192 }
5193 "report-status-v2" => {
5194 reject_capability_value("receive-pack report-status-v2", capability)?;
5195 if features.report_status_v2 {
5196 return Err(GitError::InvalidFormat(
5197 "receive-pack has duplicate report-status-v2 capability".into(),
5198 ));
5199 }
5200 features.report_status_v2 = true;
5201 }
5202 "delete-refs" => {
5203 reject_capability_value("receive-pack delete-refs", capability)?;
5204 if features.delete_refs {
5205 return Err(GitError::InvalidFormat(
5206 "receive-pack has duplicate delete-refs capability".into(),
5207 ));
5208 }
5209 features.delete_refs = true;
5210 }
5211 "ofs-delta" => {
5212 reject_capability_value("receive-pack ofs-delta", capability)?;
5213 if features.ofs_delta {
5214 return Err(GitError::InvalidFormat(
5215 "receive-pack has duplicate ofs-delta capability".into(),
5216 ));
5217 }
5218 features.ofs_delta = true;
5219 }
5220 "atomic" => {
5221 reject_capability_value("receive-pack atomic", capability)?;
5222 if features.atomic {
5223 return Err(GitError::InvalidFormat(
5224 "receive-pack has duplicate atomic capability".into(),
5225 ));
5226 }
5227 features.atomic = true;
5228 }
5229 "push-options" => {
5230 reject_capability_value("receive-pack push-options", capability)?;
5231 if features.push_options {
5232 return Err(GitError::InvalidFormat(
5233 "receive-pack has duplicate push-options capability".into(),
5234 ));
5235 }
5236 features.push_options = true;
5237 }
5238 "side-band-64k" => {
5239 reject_capability_value("receive-pack side-band-64k", capability)?;
5240 if features.side_band_64k {
5241 return Err(GitError::InvalidFormat(
5242 "receive-pack has duplicate side-band-64k capability".into(),
5243 ));
5244 }
5245 features.side_band_64k = true;
5246 }
5247 "quiet" => {
5248 reject_capability_value("receive-pack quiet", capability)?;
5249 if features.quiet {
5250 return Err(GitError::InvalidFormat(
5251 "receive-pack has duplicate quiet capability".into(),
5252 ));
5253 }
5254 features.quiet = true;
5255 }
5256 "no-thin" => {
5257 reject_capability_value("receive-pack no-thin", capability)?;
5258 if features.no_thin {
5259 return Err(GitError::InvalidFormat(
5260 "receive-pack has duplicate no-thin capability".into(),
5261 ));
5262 }
5263 features.no_thin = true;
5264 }
5265 "agent" => {
5266 let Some(agent) = &capability.value else {
5267 return Err(GitError::InvalidFormat(
5268 "receive-pack agent capability is missing value".into(),
5269 ));
5270 };
5271 if features.agent.is_some() {
5272 return Err(GitError::InvalidFormat(
5273 "receive-pack has duplicate agent capability".into(),
5274 ));
5275 }
5276 validate_capability_field("receive-pack agent", agent)?;
5277 features.agent = Some(agent.clone());
5278 }
5279 "object-format" => {
5280 let Some(format) = &capability.value else {
5281 return Err(GitError::InvalidFormat(
5282 "receive-pack object-format capability is missing value".into(),
5283 ));
5284 };
5285 if features.object_format.is_some() {
5286 return Err(GitError::InvalidFormat(
5287 "receive-pack has duplicate object-format capability".into(),
5288 ));
5289 }
5290 validate_capability_field("receive-pack object-format", format)?;
5291 features.object_format = Some(format.parse()?);
5292 }
5293 _ => {
5294 encode_capabilities(std::slice::from_ref(capability))?;
5295 if features
5296 .unknown
5297 .iter()
5298 .any(|known| known.name == capability.name)
5299 {
5300 return Err(GitError::InvalidFormat(format!(
5301 "receive-pack has duplicate {} capability",
5302 capability.name
5303 )));
5304 }
5305 features.unknown.push(capability.clone());
5306 }
5307 }
5308 }
5309 Ok(features)
5310}
5311
5312pub fn encode_receive_pack_features(features: &ReceivePackFeatures) -> Result<Vec<Capability>> {
5313 let mut capabilities = Vec::new();
5314 if features.report_status {
5315 capabilities.push(Capability {
5316 name: "report-status".into(),
5317 value: None,
5318 });
5319 }
5320 if features.report_status_v2 {
5321 capabilities.push(Capability {
5322 name: "report-status-v2".into(),
5323 value: None,
5324 });
5325 }
5326 if features.delete_refs {
5327 capabilities.push(Capability {
5328 name: "delete-refs".into(),
5329 value: None,
5330 });
5331 }
5332 if features.ofs_delta {
5333 capabilities.push(Capability {
5334 name: "ofs-delta".into(),
5335 value: None,
5336 });
5337 }
5338 if features.atomic {
5339 capabilities.push(Capability {
5340 name: "atomic".into(),
5341 value: None,
5342 });
5343 }
5344 if features.push_options {
5345 capabilities.push(Capability {
5346 name: "push-options".into(),
5347 value: None,
5348 });
5349 }
5350 if features.side_band_64k {
5351 capabilities.push(Capability {
5352 name: "side-band-64k".into(),
5353 value: None,
5354 });
5355 }
5356 if features.quiet {
5357 capabilities.push(Capability {
5358 name: "quiet".into(),
5359 value: None,
5360 });
5361 }
5362 if features.no_thin {
5363 capabilities.push(Capability {
5364 name: "no-thin".into(),
5365 value: None,
5366 });
5367 }
5368 if let Some(agent) = &features.agent {
5369 validate_capability_field("receive-pack agent", agent)?;
5370 capabilities.push(Capability {
5371 name: "agent".into(),
5372 value: Some(agent.clone()),
5373 });
5374 }
5375 if let Some(format) = features.object_format {
5376 capabilities.push(Capability {
5377 name: "object-format".into(),
5378 value: Some(format.name().into()),
5379 });
5380 }
5381 for capability in &features.unknown {
5382 if is_known_receive_pack_capability(&capability.name) {
5383 return Err(GitError::InvalidFormat(format!(
5384 "receive-pack unknown capability duplicates known capability {}",
5385 capability.name
5386 )));
5387 }
5388 encode_capabilities(std::slice::from_ref(capability))?;
5389 capabilities.push(capability.clone());
5390 }
5391 Ok(capabilities)
5392}
5393
5394pub fn validate_receive_pack_push_request_features(
5395 features: &ReceivePackFeatures,
5396 request: &ReceivePackPushRequest,
5397) -> Result<()> {
5398 for capability in &request.commands.capabilities {
5399 if matches!(
5400 capability.name.as_str(),
5401 "report-status"
5402 | "report-status-v2"
5403 | "delete-refs"
5404 | "ofs-delta"
5405 | "atomic"
5406 | "push-options"
5407 | "side-band-64k"
5408 | "quiet"
5409 | "no-thin"
5410 ) {
5411 reject_capability_value("receive-pack request capability", capability)?;
5412 }
5413 match capability.name.as_str() {
5414 "report-status" if !features.report_status => {
5415 return Err(GitError::InvalidFormat(
5416 "receive-pack request uses report-status without advertised capability".into(),
5417 ));
5418 }
5419 "report-status-v2" if !features.report_status_v2 => {
5420 return Err(GitError::InvalidFormat(
5421 "receive-pack request uses report-status-v2 without advertised capability"
5422 .into(),
5423 ));
5424 }
5425 "delete-refs" if !features.delete_refs => {
5426 return Err(GitError::InvalidFormat(
5427 "receive-pack request uses delete-refs without advertised capability".into(),
5428 ));
5429 }
5430 "ofs-delta" if !features.ofs_delta => {
5431 return Err(GitError::InvalidFormat(
5432 "receive-pack request uses ofs-delta without advertised capability".into(),
5433 ));
5434 }
5435 "atomic" if !features.atomic => {
5436 return Err(GitError::InvalidFormat(
5437 "receive-pack request uses atomic without advertised capability".into(),
5438 ));
5439 }
5440 "push-options" if !features.push_options => {
5441 return Err(GitError::InvalidFormat(
5442 "receive-pack request uses push-options without advertised capability".into(),
5443 ));
5444 }
5445 "side-band-64k" if !features.side_band_64k => {
5446 return Err(GitError::InvalidFormat(
5447 "receive-pack request uses side-band-64k without advertised capability".into(),
5448 ));
5449 }
5450 "quiet" if !features.quiet => {
5451 return Err(GitError::InvalidFormat(
5452 "receive-pack request uses quiet without advertised capability".into(),
5453 ));
5454 }
5455 "no-thin" => {
5456 return Err(GitError::InvalidFormat(
5457 "receive-pack request must not request no-thin".into(),
5458 ));
5459 }
5460 "agent" => {
5461 validate_capability_field(
5462 "receive-pack request agent",
5463 capability.value.as_deref().unwrap_or_default(),
5464 )?;
5465 }
5466 "object-format" => {
5467 let Some(value) = &capability.value else {
5468 return Err(GitError::InvalidFormat(
5469 "receive-pack request object-format capability is missing value".into(),
5470 ));
5471 };
5472 let requested_format: ObjectFormat = value.parse()?;
5473 if features.object_format != Some(requested_format) {
5474 return Err(GitError::InvalidFormat(
5475 "receive-pack request object-format was not advertised".into(),
5476 ));
5477 }
5478 }
5479 name if is_known_receive_pack_capability(name) => {}
5480 _ => {
5481 if !features
5482 .unknown
5483 .iter()
5484 .any(|advertised| advertised.name == capability.name)
5485 {
5486 return Err(GitError::InvalidFormat(format!(
5487 "receive-pack request uses unadvertised capability {}",
5488 capability.name
5489 )));
5490 }
5491 }
5492 }
5493 }
5494
5495 let requested_push_options = request
5496 .commands
5497 .capabilities
5498 .iter()
5499 .any(|capability| capability.name == "push-options");
5500 match (requested_push_options, &request.push_options) {
5501 (true, Some(_)) => {}
5502 (true, None) => {
5503 return Err(GitError::InvalidFormat(
5504 "receive-pack request uses push-options without push-options section".into(),
5505 ));
5506 }
5507 (false, Some(_)) => {
5508 return Err(GitError::InvalidFormat(
5509 "receive-pack request has push-options section without requested capability".into(),
5510 ));
5511 }
5512 (false, None) => {}
5513 }
5514
5515 let has_delete = request
5516 .commands
5517 .commands
5518 .iter()
5519 .any(is_receive_pack_delete_command);
5520 if has_delete && !features.delete_refs {
5521 return Err(GitError::InvalidFormat(
5522 "receive-pack request deletes refs without advertised delete-refs capability".into(),
5523 ));
5524 }
5525
5526 let has_update_or_create = request
5527 .commands
5528 .commands
5529 .iter()
5530 .any(|command| !is_receive_pack_delete_command(command));
5531 if has_update_or_create && request.packfile.is_empty() {
5532 return Err(GitError::InvalidFormat(
5533 "receive-pack request with create/update commands is missing packfile".into(),
5534 ));
5535 }
5536 if !has_update_or_create && !request.packfile.is_empty() {
5537 return Err(GitError::InvalidFormat(
5538 "receive-pack delete-only request must not include packfile".into(),
5539 ));
5540 }
5541 Ok(())
5542}
5543
5544pub fn apply_receive_pack_push_request<R, I, C, U, D>(
5545 features: &ReceivePackFeatures,
5546 request: &ReceivePackPushRequest,
5547 mut read_ref: R,
5548 mut install_pack: I,
5549 mut contains_object: C,
5550 mut apply_updates: U,
5551 mut delete_ref: D,
5552) -> Result<ReceivePackReportStatus>
5553where
5554 R: FnMut(&str) -> Result<Option<ObjectId>>,
5555 I: FnMut(&[u8]) -> Result<()>,
5556 C: FnMut(&ObjectId) -> Result<bool>,
5557 U: FnMut(&[ReceivePackCommand]) -> Result<()>,
5558 D: FnMut(&ReceivePackCommand) -> Result<()>,
5559{
5560 validate_receive_pack_push_request_features(features, request)?;
5561
5562 for command in request
5563 .commands
5564 .commands
5565 .iter()
5566 .filter(|command| is_receive_pack_delete_command(command))
5567 {
5568 if !command.old_id.is_null() && read_ref(&command.name)? != Some(command.old_id.clone()) {
5569 return Err(GitError::Transaction(format!(
5570 "expected ref {} to match",
5571 command.name
5572 )));
5573 }
5574 }
5575
5576 let updates = request
5577 .commands
5578 .commands
5579 .iter()
5580 .filter(|command| !is_receive_pack_delete_command(command))
5581 .cloned()
5582 .collect::<Vec<_>>();
5583 if !updates.is_empty() {
5584 install_pack(&request.packfile)?;
5585 for command in &updates {
5586 if !contains_object(&command.new_id)? {
5587 return Err(GitError::InvalidObject(format!(
5588 "receive-pack packfile did not provide {}",
5589 command.new_id
5590 )));
5591 }
5592 }
5593 apply_updates(&updates)?;
5594 }
5595
5596 for command in request
5597 .commands
5598 .commands
5599 .iter()
5600 .filter(|command| is_receive_pack_delete_command(command))
5601 {
5602 delete_ref(command)?;
5603 }
5604
5605 Ok(ReceivePackReportStatus {
5606 unpack: ReceivePackUnpackStatus::Ok,
5607 commands: request
5608 .commands
5609 .commands
5610 .iter()
5611 .map(|command| ReceivePackCommandStatus::Ok {
5612 name: command.name.clone(),
5613 })
5614 .collect(),
5615 })
5616}
5617
5618pub fn parse_receive_pack_report_status(
5619 frames: &[PktLineFrame],
5620) -> Result<ReceivePackReportStatus> {
5621 let Some((first, rest)) = frames.split_first() else {
5622 return Err(GitError::InvalidFormat(
5623 "receive-pack report-status is empty".into(),
5624 ));
5625 };
5626 let PktLineFrame::Data(payload) = first else {
5627 return Err(GitError::InvalidFormat(
5628 "receive-pack report-status must start with unpack status".into(),
5629 ));
5630 };
5631 let unpack = parse_receive_pack_unpack_status(payload)?;
5632
5633 let mut commands = Vec::new();
5634 let mut saw_flush = false;
5635 for (idx, frame) in rest.iter().enumerate() {
5636 match frame {
5637 PktLineFrame::Data(payload) if !saw_flush => {
5638 commands.push(parse_receive_pack_command_status(payload)?);
5639 }
5640 PktLineFrame::Data(_) => {
5641 return Err(GitError::InvalidFormat(
5642 "receive-pack report-status has data after flush".into(),
5643 ));
5644 }
5645 PktLineFrame::Flush => {
5646 saw_flush = true;
5647 if idx + 1 != rest.len() {
5648 return Err(GitError::InvalidFormat(
5649 "receive-pack report-status has frames after flush".into(),
5650 ));
5651 }
5652 }
5653 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5654 return Err(GitError::InvalidFormat(
5655 "receive-pack report-status contains a non-flush control packet".into(),
5656 ));
5657 }
5658 }
5659 }
5660 if !saw_flush {
5661 return Err(GitError::InvalidFormat(
5662 "receive-pack report-status missing flush".into(),
5663 ));
5664 }
5665 Ok(ReceivePackReportStatus { unpack, commands })
5666}
5667
5668pub fn encode_receive_pack_report_status(
5669 report: &ReceivePackReportStatus,
5670) -> Result<Vec<PktLineFrame>> {
5671 let mut frames = Vec::new();
5672 frames.push(PktLineFrame::data(line_from_str(
5673 &format_receive_pack_unpack_status(&report.unpack)?,
5674 ))?);
5675 for command in &report.commands {
5676 frames.push(PktLineFrame::data(line_from_str(
5677 &format_receive_pack_command_status(command)?,
5678 ))?);
5679 }
5680 frames.push(PktLineFrame::Flush);
5681 Ok(frames)
5682}
5683
5684pub fn read_receive_pack_report_status(reader: &mut impl Read) -> Result<ReceivePackReportStatus> {
5685 let frames = read_pkt_line_frames_until_flush(reader)?;
5686 parse_receive_pack_report_status(&frames)
5687}
5688
5689pub fn write_receive_pack_report_status(
5690 writer: &mut impl Write,
5691 report: &ReceivePackReportStatus,
5692) -> Result<()> {
5693 write_pkt_line_payload(
5694 writer,
5695 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5696 )?;
5697 for command in &report.commands {
5698 write_pkt_line_payload(
5699 writer,
5700 &line_from_str(&format_receive_pack_command_status(command)?),
5701 )?;
5702 }
5703 writer.write_all(b"0000")?;
5704 Ok(())
5705}
5706
5707pub fn parse_receive_pack_report_status_v2(
5708 format: ObjectFormat,
5709 frames: &[PktLineFrame],
5710) -> Result<ReceivePackReportStatusV2> {
5711 let Some((first, rest)) = frames.split_first() else {
5712 return Err(GitError::InvalidFormat(
5713 "receive-pack report-status-v2 is empty".into(),
5714 ));
5715 };
5716 let PktLineFrame::Data(payload) = first else {
5717 return Err(GitError::InvalidFormat(
5718 "receive-pack report-status-v2 must start with unpack status".into(),
5719 ));
5720 };
5721 let unpack = parse_receive_pack_unpack_status(payload)?;
5722
5723 let mut commands = Vec::new();
5724 let mut saw_flush = false;
5725 for (idx, frame) in rest.iter().enumerate() {
5726 match frame {
5727 PktLineFrame::Data(payload) if !saw_flush => {
5728 let text =
5729 parse_protocol_v2_line_text("receive-pack report-status-v2 line", payload)?;
5730 if text.starts_with("option ") {
5731 let Some(ReceivePackCommandStatusV2::Ok { options, .. }) = commands.last_mut()
5732 else {
5733 return Err(GitError::InvalidFormat(
5734 "receive-pack report-status-v2 option without ok status".into(),
5735 ));
5736 };
5737 parse_receive_pack_report_status_v2_option(format, text, options)?;
5738 } else {
5739 commands.push(parse_receive_pack_command_status_v2(text)?);
5740 }
5741 }
5742 PktLineFrame::Data(_) => {
5743 return Err(GitError::InvalidFormat(
5744 "receive-pack report-status-v2 has data after flush".into(),
5745 ));
5746 }
5747 PktLineFrame::Flush => {
5748 saw_flush = true;
5749 if idx + 1 != rest.len() {
5750 return Err(GitError::InvalidFormat(
5751 "receive-pack report-status-v2 has frames after flush".into(),
5752 ));
5753 }
5754 }
5755 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5756 return Err(GitError::InvalidFormat(
5757 "receive-pack report-status-v2 contains a non-flush control packet".into(),
5758 ));
5759 }
5760 }
5761 }
5762 if !saw_flush {
5763 return Err(GitError::InvalidFormat(
5764 "receive-pack report-status-v2 missing flush".into(),
5765 ));
5766 }
5767 Ok(ReceivePackReportStatusV2 { unpack, commands })
5768}
5769
5770pub fn encode_receive_pack_report_status_v2(
5771 report: &ReceivePackReportStatusV2,
5772) -> Result<Vec<PktLineFrame>> {
5773 let mut frames = Vec::new();
5774 frames.push(PktLineFrame::data(line_from_str(
5775 &format_receive_pack_unpack_status(&report.unpack)?,
5776 ))?);
5777 for command in &report.commands {
5778 frames.push(PktLineFrame::data(line_from_str(
5779 &format_receive_pack_command_status_v2(command)?,
5780 ))?);
5781 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5782 for option in format_receive_pack_report_status_v2_options(options)? {
5783 frames.push(PktLineFrame::data(line_from_str(&option))?);
5784 }
5785 }
5786 }
5787 frames.push(PktLineFrame::Flush);
5788 Ok(frames)
5789}
5790
5791pub fn read_receive_pack_report_status_v2(
5792 format: ObjectFormat,
5793 reader: &mut impl Read,
5794) -> Result<ReceivePackReportStatusV2> {
5795 let frames = read_pkt_line_frames_until_flush(reader)?;
5796 parse_receive_pack_report_status_v2(format, &frames)
5797}
5798
5799pub fn write_receive_pack_report_status_v2(
5800 writer: &mut impl Write,
5801 report: &ReceivePackReportStatusV2,
5802) -> Result<()> {
5803 write_pkt_line_payload(
5804 writer,
5805 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5806 )?;
5807 for command in &report.commands {
5808 write_pkt_line_payload(
5809 writer,
5810 &line_from_str(&format_receive_pack_command_status_v2(command)?),
5811 )?;
5812 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5813 for option in format_receive_pack_report_status_v2_options(options)? {
5814 write_pkt_line_payload(writer, &line_from_str(&option))?;
5815 }
5816 }
5817 }
5818 writer.write_all(b"0000")?;
5819 Ok(())
5820}
5821
5822pub fn parse_receive_pack_push_options(frames: &[PktLineFrame]) -> Result<Vec<String>> {
5823 let mut options = Vec::new();
5824 let mut saw_flush = false;
5825 for (idx, frame) in frames.iter().enumerate() {
5826 match frame {
5827 PktLineFrame::Data(payload) if !saw_flush => {
5828 let option = trim_trailing_lf(payload);
5829 validate_receive_pack_push_option(option)?;
5830 options.push(
5831 std::str::from_utf8(option)
5832 .map_err(|err| GitError::InvalidFormat(err.to_string()))?
5833 .to_string(),
5834 );
5835 }
5836 PktLineFrame::Data(_) => {
5837 return Err(GitError::InvalidFormat(
5838 "receive-pack push-options has data after flush".into(),
5839 ));
5840 }
5841 PktLineFrame::Flush => {
5842 saw_flush = true;
5843 if idx + 1 != frames.len() {
5844 return Err(GitError::InvalidFormat(
5845 "receive-pack push-options has frames after flush".into(),
5846 ));
5847 }
5848 }
5849 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5850 return Err(GitError::InvalidFormat(
5851 "receive-pack push-options contains a non-flush control packet".into(),
5852 ));
5853 }
5854 }
5855 }
5856 if !saw_flush {
5857 return Err(GitError::InvalidFormat(
5858 "receive-pack push-options missing flush".into(),
5859 ));
5860 }
5861 Ok(options)
5862}
5863
5864pub fn encode_receive_pack_push_options(options: &[String]) -> Result<Vec<PktLineFrame>> {
5865 let mut frames = Vec::new();
5866 for option in options {
5867 validate_receive_pack_push_option(option.as_bytes())?;
5868 let mut payload = option.as_bytes().to_vec();
5869 payload.push(b'\n');
5870 frames.push(PktLineFrame::data(payload)?);
5871 }
5872 frames.push(PktLineFrame::Flush);
5873 Ok(frames)
5874}
5875
5876pub fn read_receive_pack_push_options(reader: &mut impl Read) -> Result<Vec<String>> {
5877 let frames = read_pkt_line_frames_until_flush(reader)?;
5878 parse_receive_pack_push_options(&frames)
5879}
5880
5881pub fn write_receive_pack_push_options(writer: &mut impl Write, options: &[String]) -> Result<()> {
5882 for option in options {
5883 validate_receive_pack_push_option(option.as_bytes())?;
5884 let mut payload = option.as_bytes().to_vec();
5885 payload.push(b'\n');
5886 write_pkt_line_payload(writer, &payload)?;
5887 }
5888 writer.write_all(b"0000")?;
5889 Ok(())
5890}
5891
5892fn parse_receive_pack_command(format: ObjectFormat, payload: &[u8]) -> Result<ReceivePackCommand> {
5893 let text =
5894 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
5895 let mut fields = text.split(' ');
5896 let old_id = fields
5897 .next()
5898 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing old id".into()))?;
5899 let new_id = fields
5900 .next()
5901 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing new id".into()))?;
5902 let name = fields
5903 .next()
5904 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing ref name".into()))?;
5905 if fields.next().is_some() {
5906 return Err(GitError::InvalidFormat(
5907 "receive-pack command has too many fields".into(),
5908 ));
5909 }
5910 validate_protocol_v2_token("receive-pack old id", old_id)?;
5911 validate_protocol_v2_token("receive-pack new id", new_id)?;
5912 validate_protocol_v2_token("receive-pack ref name", name)?;
5913 Ok(ReceivePackCommand {
5914 old_id: ObjectId::from_hex(format, old_id)?,
5915 new_id: ObjectId::from_hex(format, new_id)?,
5916 name: name.to_string(),
5917 })
5918}
5919
5920fn format_receive_pack_command(command: &ReceivePackCommand) -> Result<Vec<u8>> {
5921 if command.old_id.format() != command.new_id.format() {
5922 return Err(GitError::InvalidObjectId(
5923 "receive-pack command object formats do not match".into(),
5924 ));
5925 }
5926 validate_protocol_v2_token("receive-pack ref name", &command.name)?;
5927 Ok(format!("{} {} {}", command.old_id, command.new_id, command.name).into_bytes())
5928}
5929
5930fn set_upload_pack_flag(value: &mut bool, capability: &Capability) -> Result<()> {
5931 reject_capability_value("upload-pack capability", capability)?;
5932 if *value {
5933 return Err(GitError::InvalidFormat(format!(
5934 "upload-pack has duplicate {} capability",
5935 capability.name
5936 )));
5937 }
5938 *value = true;
5939 Ok(())
5940}
5941
5942fn push_upload_pack_flag(capabilities: &mut Vec<Capability>, name: &str, enabled: bool) {
5943 if enabled {
5944 capabilities.push(Capability {
5945 name: name.into(),
5946 value: None,
5947 });
5948 }
5949}
5950
5951fn is_known_upload_pack_capability(name: &str) -> bool {
5952 matches!(
5953 name,
5954 "multi_ack"
5955 | "multi_ack_detailed"
5956 | "no-done"
5957 | "thin-pack"
5958 | "side-band"
5959 | "side-band-64k"
5960 | "ofs-delta"
5961 | "shallow"
5962 | "deepen-since"
5963 | "deepen-not"
5964 | "include-tag"
5965 | "no-progress"
5966 | "allow-tip-sha1-in-want"
5967 | "allow-reachable-sha1-in-want"
5968 | "filter"
5969 | "agent"
5970 | "object-format"
5971 | "symref"
5972 )
5973}
5974
5975fn is_upload_pack_flag_capability(name: &str) -> bool {
5976 matches!(
5977 name,
5978 "multi_ack"
5979 | "multi_ack_detailed"
5980 | "no-done"
5981 | "thin-pack"
5982 | "side-band"
5983 | "side-band-64k"
5984 | "ofs-delta"
5985 | "shallow"
5986 | "deepen-since"
5987 | "deepen-not"
5988 | "include-tag"
5989 | "no-progress"
5990 | "allow-tip-sha1-in-want"
5991 | "allow-reachable-sha1-in-want"
5992 | "filter"
5993 )
5994}
5995
5996fn reject_capability_value(label: &str, capability: &Capability) -> Result<()> {
5997 if capability.value.is_some() {
5998 return Err(GitError::InvalidFormat(format!(
5999 "{label} must not have value"
6000 )));
6001 }
6002 Ok(())
6003}
6004
6005fn is_known_receive_pack_capability(name: &str) -> bool {
6006 matches!(
6007 name,
6008 "report-status"
6009 | "report-status-v2"
6010 | "delete-refs"
6011 | "ofs-delta"
6012 | "atomic"
6013 | "push-options"
6014 | "side-band-64k"
6015 | "quiet"
6016 | "no-thin"
6017 | "agent"
6018 | "object-format"
6019 )
6020}
6021
6022fn is_receive_pack_delete_command(command: &ReceivePackCommand) -> bool {
6023 command.new_id.is_null()
6024}
6025
6026fn parse_receive_pack_unpack_status(payload: &[u8]) -> Result<ReceivePackUnpackStatus> {
6027 let text = parse_protocol_v2_line_text("receive-pack unpack status", payload)?;
6028 if text == "unpack ok" {
6029 return Ok(ReceivePackUnpackStatus::Ok);
6030 }
6031 let Some(message) = text.strip_prefix("unpack ") else {
6032 return Err(GitError::InvalidFormat(format!(
6033 "unsupported receive-pack unpack status {text}"
6034 )));
6035 };
6036 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6037 Ok(ReceivePackUnpackStatus::Error(message.to_string()))
6038}
6039
6040fn format_receive_pack_unpack_status(status: &ReceivePackUnpackStatus) -> Result<String> {
6041 match status {
6042 ReceivePackUnpackStatus::Ok => Ok("unpack ok".into()),
6043 ReceivePackUnpackStatus::Error(message) => {
6044 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6045 Ok(format!("unpack {message}"))
6046 }
6047 }
6048}
6049
6050fn parse_receive_pack_command_status(payload: &[u8]) -> Result<ReceivePackCommandStatus> {
6051 let text = parse_protocol_v2_line_text("receive-pack command status", payload)?;
6052 if let Some(name) = text.strip_prefix("ok ") {
6053 validate_protocol_v2_token("receive-pack status ref name", name)?;
6054 return Ok(ReceivePackCommandStatus::Ok {
6055 name: name.to_string(),
6056 });
6057 }
6058 let Some(rest) = text.strip_prefix("ng ") else {
6059 return Err(GitError::InvalidFormat(format!(
6060 "unsupported receive-pack command status {text}"
6061 )));
6062 };
6063 let (name, message) = rest
6064 .split_once(' ')
6065 .ok_or_else(|| GitError::InvalidFormat("receive-pack ng status missing message".into()))?;
6066 validate_protocol_v2_token("receive-pack status ref name", name)?;
6067 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6068 Ok(ReceivePackCommandStatus::Ng {
6069 name: name.to_string(),
6070 message: message.to_string(),
6071 })
6072}
6073
6074fn format_receive_pack_command_status(status: &ReceivePackCommandStatus) -> Result<String> {
6075 match status {
6076 ReceivePackCommandStatus::Ok { name } => {
6077 validate_protocol_v2_token("receive-pack status ref name", name)?;
6078 Ok(format!("ok {name}"))
6079 }
6080 ReceivePackCommandStatus::Ng { name, message } => {
6081 validate_protocol_v2_token("receive-pack status ref name", name)?;
6082 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6083 Ok(format!("ng {name} {message}"))
6084 }
6085 }
6086}
6087
6088fn parse_receive_pack_command_status_v2(text: &str) -> Result<ReceivePackCommandStatusV2> {
6089 if let Some(name) = text.strip_prefix("ok ") {
6090 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6091 return Ok(ReceivePackCommandStatusV2::Ok {
6092 name: name.to_string(),
6093 options: ReceivePackCommandStatusV2Options::default(),
6094 });
6095 }
6096 let Some(rest) = text.strip_prefix("ng ") else {
6097 return Err(GitError::InvalidFormat(format!(
6098 "unsupported receive-pack report-status-v2 line {text}"
6099 )));
6100 };
6101 let (name, message) = rest.split_once(' ').ok_or_else(|| {
6102 GitError::InvalidFormat("receive-pack status-v2 ng status missing message".into())
6103 })?;
6104 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6105 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6106 Ok(ReceivePackCommandStatusV2::Ng {
6107 name: name.to_string(),
6108 message: message.to_string(),
6109 })
6110}
6111
6112fn format_receive_pack_command_status_v2(status: &ReceivePackCommandStatusV2) -> Result<String> {
6113 match status {
6114 ReceivePackCommandStatusV2::Ok { name, .. } => {
6115 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6116 Ok(format!("ok {name}"))
6117 }
6118 ReceivePackCommandStatusV2::Ng { name, message } => {
6119 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6120 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6121 Ok(format!("ng {name} {message}"))
6122 }
6123 }
6124}
6125
6126fn parse_receive_pack_report_status_v2_option(
6127 format: ObjectFormat,
6128 text: &str,
6129 options: &mut ReceivePackCommandStatusV2Options,
6130) -> Result<()> {
6131 if let Some(refname) = text.strip_prefix("option refname ") {
6132 if options.refname.is_some() {
6133 return Err(GitError::InvalidFormat(
6134 "receive-pack report-status-v2 has duplicate refname option".into(),
6135 ));
6136 }
6137 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6138 options.refname = Some(refname.to_string());
6139 } else if let Some(old_oid) = text.strip_prefix("option old-oid ") {
6140 if options.old_oid.is_some() {
6141 return Err(GitError::InvalidFormat(
6142 "receive-pack report-status-v2 has duplicate old-oid option".into(),
6143 ));
6144 }
6145 validate_protocol_v2_token("receive-pack status-v2 option old-oid", old_oid)?;
6146 options.old_oid = Some(ObjectId::from_hex(format, old_oid)?);
6147 } else if let Some(new_oid) = text.strip_prefix("option new-oid ") {
6148 if options.new_oid.is_some() {
6149 return Err(GitError::InvalidFormat(
6150 "receive-pack report-status-v2 has duplicate new-oid option".into(),
6151 ));
6152 }
6153 validate_protocol_v2_token("receive-pack status-v2 option new-oid", new_oid)?;
6154 options.new_oid = Some(ObjectId::from_hex(format, new_oid)?);
6155 } else if text == "option forced-update" {
6156 if options.forced_update {
6157 return Err(GitError::InvalidFormat(
6158 "receive-pack report-status-v2 has duplicate forced-update option".into(),
6159 ));
6160 }
6161 options.forced_update = true;
6162 } else {
6163 return Err(GitError::InvalidFormat(format!(
6164 "unsupported receive-pack report-status-v2 option {text}"
6165 )));
6166 }
6167 Ok(())
6168}
6169
6170fn format_receive_pack_report_status_v2_options(
6171 options: &ReceivePackCommandStatusV2Options,
6172) -> Result<Vec<String>> {
6173 let mut out = Vec::new();
6174 if let Some(refname) = &options.refname {
6175 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6176 out.push(format!("option refname {refname}"));
6177 }
6178 if let Some(old_oid) = &options.old_oid {
6179 out.push(format!("option old-oid {old_oid}"));
6180 }
6181 if let Some(new_oid) = &options.new_oid {
6182 out.push(format!("option new-oid {new_oid}"));
6183 }
6184 if options.forced_update {
6185 out.push("option forced-update".into());
6186 }
6187 Ok(out)
6188}
6189
6190fn validate_receive_pack_status_message(label: &str, message: &str) -> Result<()> {
6191 if message.is_empty() {
6192 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6193 }
6194 if message
6195 .bytes()
6196 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6197 {
6198 return Err(GitError::InvalidFormat(format!(
6199 "{label} contains a delimiter byte"
6200 )));
6201 }
6202 Ok(())
6203}
6204
6205fn validate_receive_pack_push_option(option: &[u8]) -> Result<()> {
6206 if option.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6207 return Err(GitError::InvalidFormat(
6208 "receive-pack push-option contains a delimiter byte".into(),
6209 ));
6210 }
6211 Ok(())
6212}
6213
6214fn validate_protocol_error_message(message: &str) -> Result<()> {
6215 if message.is_empty() {
6216 return Err(GitError::InvalidFormat(
6217 "protocol error message is empty".into(),
6218 ));
6219 }
6220 if message
6221 .bytes()
6222 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6223 {
6224 return Err(GitError::InvalidFormat(
6225 "protocol error message contains a delimiter byte".into(),
6226 ));
6227 }
6228 Ok(())
6229}
6230
6231fn parse_capability_token(token: &str) -> Result<Capability> {
6232 if token.is_empty() {
6233 return Err(GitError::InvalidFormat("empty capability token".into()));
6234 }
6235 let (name, value) = token
6236 .split_once('=')
6237 .map_or((token, None), |(name, value)| (name, Some(value)));
6238 validate_capability_field("capability name", name)?;
6239 if let Some(value) = value {
6240 validate_capability_field("capability value", value)?;
6241 }
6242 Ok(Capability {
6243 name: name.to_string(),
6244 value: value.map(str::to_string),
6245 })
6246}
6247
6248fn parse_protocol_v2_capability_line(payload: &[u8]) -> Result<Capability> {
6249 let payload = trim_trailing_lf(payload);
6250 if payload.is_empty() {
6251 return Err(GitError::InvalidFormat(
6252 "empty protocol v2 capability line".into(),
6253 ));
6254 }
6255 let text =
6256 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6257 let (name, value) = text
6258 .split_once('=')
6259 .map_or((text, None), |(name, value)| (name, Some(value)));
6260 validate_capability_name(name)?;
6261 if let Some(value) = value {
6262 validate_protocol_v2_capability_value(value)?;
6263 }
6264 Ok(Capability {
6265 name: name.to_string(),
6266 value: value.map(str::to_string),
6267 })
6268}
6269
6270fn parse_protocol_v2_command_line(payload: &[u8]) -> Result<String> {
6271 let payload = trim_trailing_lf(payload);
6272 let text =
6273 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6274 let Some(command) = text.strip_prefix("command=") else {
6275 return Err(GitError::InvalidFormat(
6276 "protocol v2 command request missing command prefix".into(),
6277 ));
6278 };
6279 validate_capability_name(command)?;
6280 Ok(command.to_string())
6281}
6282
6283fn parse_protocol_v2_ls_refs_line(
6284 format: ObjectFormat,
6285 payload: &[u8],
6286) -> Result<ProtocolV2LsRefsRecord> {
6287 let payload = trim_trailing_lf(payload);
6288 if payload.is_empty() {
6289 return Err(GitError::InvalidFormat(
6290 "ls-refs response line is empty".into(),
6291 ));
6292 }
6293 let text =
6294 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6295 let mut fields = text.split(' ');
6296 let first = fields
6297 .next()
6298 .ok_or_else(|| GitError::InvalidFormat("ls-refs response line is empty".into()))?;
6299 if first == "unborn" {
6300 let name = fields
6301 .next()
6302 .ok_or_else(|| GitError::InvalidFormat("ls-refs unborn line is missing name".into()))?;
6303 validate_protocol_v2_token("ls-refs ref name", name)?;
6304 let (symref_target, attributes) = parse_protocol_v2_ls_refs_attributes(format, fields)?;
6305 return Ok(ProtocolV2LsRefsRecord::Unborn {
6306 name: name.to_string(),
6307 symref_target,
6308 attributes,
6309 });
6310 }
6311
6312 let oid = ObjectId::from_hex(format, first)?;
6313 let name = fields
6314 .next()
6315 .ok_or_else(|| GitError::InvalidFormat("ls-refs ref line is missing name".into()))?;
6316 validate_protocol_v2_token("ls-refs ref name", name)?;
6317 let (peeled, symref_target, attributes) =
6318 parse_protocol_v2_ls_refs_ref_attributes(format, fields)?;
6319 Ok(ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
6320 oid,
6321 name: name.to_string(),
6322 peeled,
6323 symref_target,
6324 attributes,
6325 }))
6326}
6327
6328fn parse_protocol_v2_ls_refs_ref_attributes<'a>(
6329 format: ObjectFormat,
6330 fields: impl Iterator<Item = &'a str>,
6331) -> Result<(Option<ObjectId>, Option<String>, Vec<String>)> {
6332 let mut peeled = None;
6333 let (symref_target, attributes) =
6334 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6335 if let Some(value) = attr.strip_prefix("peeled:") {
6336 if peeled.is_some() {
6337 return Err(GitError::InvalidFormat(
6338 "ls-refs response has duplicate peeled attribute".into(),
6339 ));
6340 }
6341 peeled = Some(ObjectId::from_hex(format, value)?);
6342 return Ok(true);
6343 }
6344 Ok(false)
6345 })?;
6346 Ok((peeled, symref_target, attributes))
6347}
6348
6349fn parse_protocol_v2_ls_refs_attributes<'a>(
6350 format: ObjectFormat,
6351 fields: impl Iterator<Item = &'a str>,
6352) -> Result<(Option<String>, Vec<String>)> {
6353 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6354 if attr.starts_with("peeled:") {
6355 return Err(GitError::InvalidFormat(
6356 "ls-refs unborn line has peeled attribute".into(),
6357 ));
6358 }
6359 Ok(false)
6360 })
6361}
6362
6363fn parse_protocol_v2_ls_refs_attributes_with<'a, F>(
6364 _format: ObjectFormat,
6365 fields: impl Iterator<Item = &'a str>,
6366 mut handle_known: F,
6367) -> Result<(Option<String>, Vec<String>)>
6368where
6369 F: FnMut(&str) -> Result<bool>,
6370{
6371 let mut symref_target = None;
6372 let mut attributes = Vec::new();
6373 for attr in fields {
6374 validate_protocol_v2_token("ls-refs attribute", attr)?;
6375 if let Some(value) = attr.strip_prefix("symref-target:") {
6376 if symref_target.is_some() {
6377 return Err(GitError::InvalidFormat(
6378 "ls-refs response has duplicate symref-target attribute".into(),
6379 ));
6380 }
6381 validate_protocol_v2_token("ls-refs symref-target", value)?;
6382 symref_target = Some(value.to_string());
6383 } else if !handle_known(attr)? {
6384 attributes.push(attr.to_string());
6385 }
6386 }
6387 Ok((symref_target, attributes))
6388}
6389
6390fn format_protocol_v2_ls_refs_record(record: &ProtocolV2LsRefsRecord) -> Result<String> {
6391 let mut out = String::new();
6392 match record {
6393 ProtocolV2LsRefsRecord::Ref(reference) => {
6394 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
6395 out.push_str(&reference.oid.to_string());
6396 out.push(' ');
6397 out.push_str(&reference.name);
6398 if let Some(peeled) = &reference.peeled {
6399 if peeled.format() != reference.oid.format() {
6400 return Err(GitError::InvalidObjectId(
6401 "ls-refs peeled object format does not match ref object format".into(),
6402 ));
6403 }
6404 out.push(' ');
6405 out.push_str("peeled:");
6406 out.push_str(&peeled.to_string());
6407 }
6408 if let Some(target) = &reference.symref_target {
6409 validate_protocol_v2_token("ls-refs symref-target", target)?;
6410 out.push(' ');
6411 out.push_str("symref-target:");
6412 out.push_str(target);
6413 }
6414 append_protocol_v2_ls_refs_attributes(&mut out, &reference.attributes)?;
6415 }
6416 ProtocolV2LsRefsRecord::Unborn {
6417 name,
6418 symref_target,
6419 attributes,
6420 } => {
6421 validate_protocol_v2_token("ls-refs ref name", name)?;
6422 out.push_str("unborn ");
6423 out.push_str(name);
6424 if let Some(target) = symref_target {
6425 validate_protocol_v2_token("ls-refs symref-target", target)?;
6426 out.push(' ');
6427 out.push_str("symref-target:");
6428 out.push_str(target);
6429 }
6430 append_protocol_v2_ls_refs_attributes(&mut out, attributes)?;
6431 }
6432 }
6433 Ok(out)
6434}
6435
6436fn append_protocol_v2_ls_refs_attributes(out: &mut String, attributes: &[String]) -> Result<()> {
6437 for attr in attributes {
6438 validate_protocol_v2_token("ls-refs attribute", attr)?;
6439 if attr.starts_with("peeled:") || attr.starts_with("symref-target:") {
6440 return Err(GitError::InvalidFormat(
6441 "ls-refs generic attributes must not duplicate known attributes".into(),
6442 ));
6443 }
6444 out.push(' ');
6445 out.push_str(attr);
6446 }
6447 Ok(())
6448}
6449
6450fn parse_fetch_section_header(payload: &[u8]) -> Result<String> {
6451 let name = parse_protocol_v2_line_text("fetch response section", payload)?;
6452 validate_capability_name(name)?;
6453 Ok(name.to_string())
6454}
6455
6456fn flush_terminates_protocol_v2_response(frames: &[PktLineFrame], idx: usize) -> bool {
6457 idx + 1 == frames.len()
6458 || (idx + 2 == frames.len() && matches!(frames[idx + 1], PktLineFrame::ResponseEnd))
6459}
6460
6461fn parse_fetch_section(
6462 format: ObjectFormat,
6463 name: String,
6464 lines: Vec<Vec<u8>>,
6465) -> Result<ProtocolV2FetchResponseSection> {
6466 match name.as_str() {
6467 "acknowledgments" => lines
6468 .iter()
6469 .map(|line| parse_fetch_acknowledgment(format, line))
6470 .collect::<Result<Vec<_>>>()
6471 .map(ProtocolV2FetchResponseSection::Acknowledgments),
6472 "shallow-info" => lines
6473 .iter()
6474 .map(|line| parse_fetch_shallow_info(format, line))
6475 .collect::<Result<Vec<_>>>()
6476 .map(ProtocolV2FetchResponseSection::ShallowInfo),
6477 "wanted-refs" => lines
6478 .iter()
6479 .map(|line| parse_fetch_wanted_ref(format, line))
6480 .collect::<Result<Vec<_>>>()
6481 .map(ProtocolV2FetchResponseSection::WantedRefs),
6482 "packfile-uris" => lines
6483 .iter()
6484 .map(|line| parse_fetch_packfile_uri(format, line))
6485 .collect::<Result<Vec<_>>>()
6486 .map(ProtocolV2FetchResponseSection::PackfileUris),
6487 "packfile" => Ok(ProtocolV2FetchResponseSection::Packfile(lines)),
6488 _ => Ok(ProtocolV2FetchResponseSection::Unknown { name, lines }),
6489 }
6490}
6491
6492fn parse_fetch_acknowledgment(
6493 format: ObjectFormat,
6494 line: &[u8],
6495) -> Result<ProtocolV2FetchAcknowledgment> {
6496 let text = parse_protocol_v2_line_text("fetch acknowledgment", line)?;
6497 match text {
6498 "NAK" => Ok(ProtocolV2FetchAcknowledgment::Nak),
6499 "ready" => Ok(ProtocolV2FetchAcknowledgment::Ready),
6500 value if value.starts_with("ACK ") => Ok(ProtocolV2FetchAcknowledgment::Ack(
6501 parse_oid_argument(format, "fetch ACK", value, "ACK ")?,
6502 )),
6503 other => Err(GitError::InvalidFormat(format!(
6504 "unsupported fetch acknowledgment {other}"
6505 ))),
6506 }
6507}
6508
6509fn parse_fetch_shallow_info(
6510 format: ObjectFormat,
6511 line: &[u8],
6512) -> Result<ProtocolV2FetchShallowInfo> {
6513 let text = parse_protocol_v2_line_text("fetch shallow-info", line)?;
6514 if text.starts_with("shallow ") {
6515 return Ok(ProtocolV2FetchShallowInfo::Shallow(parse_oid_argument(
6516 format,
6517 "fetch shallow",
6518 text,
6519 "shallow ",
6520 )?));
6521 }
6522 if text.starts_with("unshallow ") {
6523 return Ok(ProtocolV2FetchShallowInfo::Unshallow(parse_oid_argument(
6524 format,
6525 "fetch unshallow",
6526 text,
6527 "unshallow ",
6528 )?));
6529 }
6530 Err(GitError::InvalidFormat(format!(
6531 "unsupported fetch shallow-info {text}"
6532 )))
6533}
6534
6535fn parse_fetch_wanted_ref(format: ObjectFormat, line: &[u8]) -> Result<ProtocolV2FetchWantedRef> {
6536 let text = parse_protocol_v2_line_text("fetch wanted-ref", line)?;
6537 let (oid, name) = text
6538 .split_once(' ')
6539 .ok_or_else(|| GitError::InvalidFormat("fetch wanted-ref is missing name".into()))?;
6540 validate_protocol_v2_token("fetch wanted-ref name", name)?;
6541 Ok(ProtocolV2FetchWantedRef {
6542 oid: ObjectId::from_hex(format, oid)?,
6543 name: name.to_string(),
6544 })
6545}
6546
6547fn parse_fetch_packfile_uri(
6548 format: ObjectFormat,
6549 line: &[u8],
6550) -> Result<ProtocolV2FetchPackfileUri> {
6551 let text = parse_protocol_v2_line_text("fetch packfile-uri", line)?;
6552 let (pack_hash, uri) = text
6553 .split_once(' ')
6554 .ok_or_else(|| GitError::InvalidFormat("fetch packfile-uri is missing uri".into()))?;
6555 validate_protocol_v2_token("fetch packfile-uri hash", pack_hash)?;
6556 validate_protocol_v2_token("fetch packfile-uri", uri)?;
6557 Ok(ProtocolV2FetchPackfileUri {
6558 pack_hash: ObjectId::from_hex(format, pack_hash)?,
6559 uri: uri.to_string(),
6560 })
6561}
6562
6563fn protocol_v2_fetch_section_name(section: &ProtocolV2FetchResponseSection) -> &str {
6564 match section {
6565 ProtocolV2FetchResponseSection::Acknowledgments(_) => "acknowledgments",
6566 ProtocolV2FetchResponseSection::ShallowInfo(_) => "shallow-info",
6567 ProtocolV2FetchResponseSection::WantedRefs(_) => "wanted-refs",
6568 ProtocolV2FetchResponseSection::PackfileUris(_) => "packfile-uris",
6569 ProtocolV2FetchResponseSection::Packfile(_) => "packfile",
6570 ProtocolV2FetchResponseSection::Unknown { name, .. } => name,
6571 }
6572}
6573
6574fn format_protocol_v2_fetch_section_lines(
6575 section: &ProtocolV2FetchResponseSection,
6576) -> Result<Vec<Vec<u8>>> {
6577 match section {
6578 ProtocolV2FetchResponseSection::Acknowledgments(acks) => acks
6579 .iter()
6580 .map(|ack| match ack {
6581 ProtocolV2FetchAcknowledgment::Nak => Ok(line_from_str("NAK")),
6582 ProtocolV2FetchAcknowledgment::Ack(oid) => Ok(line_from_str(&format!("ACK {oid}"))),
6583 ProtocolV2FetchAcknowledgment::Ready => Ok(line_from_str("ready")),
6584 })
6585 .collect(),
6586 ProtocolV2FetchResponseSection::ShallowInfo(entries) => entries
6587 .iter()
6588 .map(|entry| match entry {
6589 ProtocolV2FetchShallowInfo::Shallow(oid) => {
6590 Ok(line_from_str(&format!("shallow {oid}")))
6591 }
6592 ProtocolV2FetchShallowInfo::Unshallow(oid) => {
6593 Ok(line_from_str(&format!("unshallow {oid}")))
6594 }
6595 })
6596 .collect(),
6597 ProtocolV2FetchResponseSection::WantedRefs(refs) => refs
6598 .iter()
6599 .map(|wanted| {
6600 validate_protocol_v2_token("fetch wanted-ref name", &wanted.name)?;
6601 Ok(line_from_str(&format!("{} {}", wanted.oid, wanted.name)))
6602 })
6603 .collect(),
6604 ProtocolV2FetchResponseSection::PackfileUris(uris) => uris
6605 .iter()
6606 .map(|packfile_uri| {
6607 validate_protocol_v2_token("fetch packfile-uri", &packfile_uri.uri)?;
6608 Ok(line_from_str(&format!(
6609 "{} {}",
6610 packfile_uri.pack_hash, packfile_uri.uri
6611 )))
6612 })
6613 .collect(),
6614 ProtocolV2FetchResponseSection::Packfile(lines) => Ok(lines.clone()),
6615 ProtocolV2FetchResponseSection::Unknown { name, lines } => {
6616 validate_capability_name(name)?;
6617 for line in lines {
6618 validate_protocol_v2_line("fetch unknown section line", line)?;
6619 }
6620 Ok(lines.clone())
6621 }
6622 }
6623}
6624
6625fn parse_protocol_v2_object_info_record(
6626 format: ObjectFormat,
6627 line: &[u8],
6628) -> Result<ProtocolV2ObjectInfoRecord> {
6629 let text = parse_protocol_v2_line_text("object-info record", line)?;
6630 let mut fields = text.split(' ');
6631 let oid = fields
6632 .next()
6633 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing oid".into()))?;
6634 let size = fields
6635 .next()
6636 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing size".into()))?;
6637 if fields.next().is_some() {
6638 return Err(GitError::InvalidFormat(
6639 "object-info record has too many fields".into(),
6640 ));
6641 }
6642 validate_protocol_v2_token("object-info oid", oid)?;
6643 validate_protocol_v2_token("object-info size", size)?;
6644 let size = size
6645 .parse::<u64>()
6646 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6647 Ok(ProtocolV2ObjectInfoRecord {
6648 oid: ObjectId::from_hex(format, oid)?,
6649 size,
6650 })
6651}
6652
6653fn parse_dumb_http_info_ref_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpRefRecord> {
6654 validate_dumb_http_info_ref_line(line)?;
6655 let line = trim_trailing_lf(line);
6656 let tab = line
6657 .iter()
6658 .position(|byte| *byte == b'\t')
6659 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP ref record is missing name".into()))?;
6660 let (oid, name) = (&line[..tab], &line[tab + 1..]);
6661 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6662 validate_protocol_v2_token("dumb HTTP ref oid", oid)?;
6663 let name = std::str::from_utf8(name).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6664 let (name, peeled) = name
6665 .strip_suffix("^{}")
6666 .map_or((name, false), |name| (name, true));
6667 validate_dumb_http_ref_name(name)?;
6668 Ok(DumbHttpRefRecord {
6669 oid: ObjectId::from_hex(format, oid)?,
6670 name: name.to_string(),
6671 peeled,
6672 })
6673}
6674
6675fn parse_dumb_http_alternate(line: &[u8]) -> Result<String> {
6676 validate_dumb_http_alternate_line(line)?;
6677 let line = trim_trailing_lf(line);
6678 let alternate =
6679 std::str::from_utf8(line).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6680 validate_dumb_http_alternate(alternate)?;
6681 Ok(alternate.to_string())
6682}
6683
6684fn parse_dumb_http_pack_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpPackRecord> {
6685 validate_dumb_http_info_ref_line(line)?;
6686 let line = parse_protocol_v2_line_text("dumb HTTP pack record", line)?;
6687 let pack_name = line
6688 .strip_prefix("P ")
6689 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP pack record must start with P".into()))?;
6690 let hash = pack_name
6691 .strip_prefix("pack-")
6692 .and_then(|value| value.strip_suffix(".pack"))
6693 .ok_or_else(|| GitError::InvalidFormat("invalid dumb HTTP pack name".into()))?;
6694 validate_protocol_v2_token("dumb HTTP pack hash", hash)?;
6695 Ok(DumbHttpPackRecord {
6696 hash: ObjectId::from_hex(format, hash)?,
6697 })
6698}
6699
6700fn encode_protocol_v2_capability(capability: &Capability) -> Result<Vec<u8>> {
6701 validate_capability_name(&capability.name)?;
6702 let mut out = capability.name.as_bytes().to_vec();
6703 if let Some(value) = &capability.value {
6704 validate_protocol_v2_capability_value(value)?;
6705 out.push(b'=');
6706 out.extend_from_slice(value.as_bytes());
6707 }
6708 Ok(out)
6709}
6710
6711fn validate_capability_field(label: &str, value: &str) -> Result<()> {
6712 if value.is_empty() {
6713 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6714 }
6715 if value
6716 .bytes()
6717 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t' | 0))
6718 {
6719 return Err(GitError::InvalidFormat(format!(
6720 "{label} contains a delimiter byte"
6721 )));
6722 }
6723 Ok(())
6724}
6725
6726fn validate_capability_name(value: &str) -> Result<()> {
6727 validate_capability_field("capability name", value)?;
6728 if value.bytes().any(|byte| byte == b'=') {
6729 return Err(GitError::InvalidFormat(
6730 "capability name contains a delimiter byte".into(),
6731 ));
6732 }
6733 Ok(())
6734}
6735
6736fn validate_protocol_v2_capability_value(value: &str) -> Result<()> {
6737 if value.is_empty() {
6738 return Err(GitError::InvalidFormat(
6739 "protocol v2 capability value is empty".into(),
6740 ));
6741 }
6742 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6743 return Err(GitError::InvalidFormat(
6744 "protocol v2 capability value contains a delimiter byte".into(),
6745 ));
6746 }
6747 Ok(())
6748}
6749
6750fn validate_protocol_v2_argument(value: &[u8]) -> Result<()> {
6751 if value.is_empty() {
6752 return Err(GitError::InvalidFormat(
6753 "protocol v2 command argument is empty".into(),
6754 ));
6755 }
6756 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6757 return Err(GitError::InvalidFormat(
6758 "protocol v2 command argument contains a delimiter byte".into(),
6759 ));
6760 }
6761 Ok(())
6762}
6763
6764fn validate_upload_archive_argument(value: &str) -> Result<()> {
6765 if value.is_empty() {
6766 return Err(GitError::InvalidFormat(
6767 "upload-archive argument is empty".into(),
6768 ));
6769 }
6770 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6771 return Err(GitError::InvalidFormat(
6772 "upload-archive argument contains a delimiter byte".into(),
6773 ));
6774 }
6775 Ok(())
6776}
6777
6778fn validate_upload_archive_status_message(value: &str) -> Result<()> {
6779 if value.is_empty() {
6780 return Err(GitError::InvalidFormat(
6781 "upload-archive status message is empty".into(),
6782 ));
6783 }
6784 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6785 return Err(GitError::InvalidFormat(
6786 "upload-archive status message contains a delimiter byte".into(),
6787 ));
6788 }
6789 Ok(())
6790}
6791
6792fn non_empty(value: &str) -> Option<&str> {
6793 (!value.is_empty()).then_some(value)
6794}
6795
6796fn validate_refspec_value(value: &str) -> Result<()> {
6797 if value.is_empty() {
6798 return Err(GitError::InvalidFormat("refspec is empty".into()));
6799 }
6800 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6801 return Err(GitError::InvalidFormat(
6802 "refspec contains a delimiter byte".into(),
6803 ));
6804 }
6805 Ok(())
6806}
6807
6808fn validate_refspec_endpoint(label: &str, value: &str) -> Result<()> {
6809 if value.is_empty() {
6810 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6811 }
6812 if value
6813 .bytes()
6814 .any(|byte| matches!(byte, b':' | b' ' | b'\t' | b'\n' | b'\r' | 0))
6815 {
6816 return Err(GitError::InvalidFormat(format!(
6817 "{label} contains a delimiter byte"
6818 )));
6819 }
6820 Ok(())
6821}
6822
6823fn count_refspec_wildcards(value: &str) -> usize {
6824 value.bytes().filter(|byte| *byte == b'*').count()
6825}
6826
6827fn validate_refspec_shape(refspec: &RefSpec) -> Result<()> {
6828 if refspec.force && refspec.negative {
6829 return Err(GitError::InvalidFormat(
6830 "negative refspec must not be forced".into(),
6831 ));
6832 }
6833 if refspec.negative && refspec.dst.is_some() {
6834 return Err(GitError::InvalidFormat(
6835 "negative refspec must not have a destination".into(),
6836 ));
6837 }
6838 if refspec.negative && refspec.src.is_none() {
6839 return Err(GitError::InvalidFormat(
6840 "negative refspec is missing a source".into(),
6841 ));
6842 }
6843 if refspec.src.is_none() && refspec.dst.is_none() && refspec.negative {
6844 return Err(GitError::InvalidFormat(
6845 "refspec must include a source or destination".into(),
6846 ));
6847 }
6848 if let Some(src) = &refspec.src {
6849 validate_refspec_endpoint("refspec source", src)?;
6850 }
6851 if let Some(dst) = &refspec.dst {
6852 validate_refspec_endpoint("refspec destination", dst)?;
6853 }
6854 let src_pattern_count = refspec
6855 .src
6856 .as_deref()
6857 .map(count_refspec_wildcards)
6858 .unwrap_or(0);
6859 let dst_pattern_count = refspec
6860 .dst
6861 .as_deref()
6862 .map(count_refspec_wildcards)
6863 .unwrap_or(0);
6864 if src_pattern_count > 1 || dst_pattern_count > 1 {
6865 return Err(GitError::InvalidFormat(
6866 "refspec endpoint has too many wildcards".into(),
6867 ));
6868 }
6869 if refspec.dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
6870 return Err(GitError::InvalidFormat(
6871 "refspec wildcard must appear in both source and destination".into(),
6872 ));
6873 }
6874 if refspec.pattern != (src_pattern_count == 1 || dst_pattern_count == 1) {
6875 return Err(GitError::InvalidFormat(
6876 "refspec pattern flag does not match endpoints".into(),
6877 ));
6878 }
6879 Ok(())
6880}
6881
6882fn parse_fetch_head_record(format: ObjectFormat, line: &[u8]) -> Result<FetchHeadRecord> {
6883 validate_fetch_head_line(line)?;
6884 let line = trim_trailing_lf(line);
6885 let mut fields = line.splitn(3, |byte| *byte == b'\t');
6886 let oid = fields
6887 .next()
6888 .ok_or_else(|| GitError::InvalidFormat("FETCH_HEAD record is missing oid".into()))?;
6889 let merge_marker = fields.next().ok_or_else(|| {
6890 GitError::InvalidFormat("FETCH_HEAD record is missing merge marker".into())
6891 })?;
6892 let description = fields.next().ok_or_else(|| {
6893 GitError::InvalidFormat("FETCH_HEAD record is missing description".into())
6894 })?;
6895 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6896 validate_protocol_v2_token("FETCH_HEAD oid", oid)?;
6897 let not_for_merge = match merge_marker {
6898 b"" => false,
6899 b"not-for-merge" => true,
6900 _ => {
6901 return Err(GitError::InvalidFormat(
6902 "FETCH_HEAD record has invalid merge marker".into(),
6903 ));
6904 }
6905 };
6906 let description =
6907 std::str::from_utf8(description).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6908 validate_fetch_head_description_field(description)?;
6909 Ok(FetchHeadRecord {
6910 oid: ObjectId::from_hex(format, oid)?,
6911 not_for_merge,
6912 description: description.to_string(),
6913 })
6914}
6915
6916fn validate_fetch_head_line(value: &[u8]) -> Result<()> {
6917 if value.is_empty() {
6918 return Err(GitError::InvalidFormat("FETCH_HEAD record is empty".into()));
6919 }
6920 if !value.ends_with(b"\n") {
6921 return Err(GitError::InvalidFormat(
6922 "FETCH_HEAD record missing LF".into(),
6923 ));
6924 }
6925 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
6926 return Err(GitError::InvalidFormat(
6927 "FETCH_HEAD record contains a delimiter byte".into(),
6928 ));
6929 }
6930 Ok(())
6931}
6932
6933fn validate_fetch_head_description_field(value: &str) -> Result<()> {
6934 if value.is_empty() {
6935 return Err(GitError::InvalidFormat(
6936 "FETCH_HEAD description is empty".into(),
6937 ));
6938 }
6939 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6940 return Err(GitError::InvalidFormat(
6941 "FETCH_HEAD description contains a delimiter byte".into(),
6942 ));
6943 }
6944 Ok(())
6945}
6946
6947fn refspec_is_excluded(negative: &[&RefSpec], source: &str) -> Result<bool> {
6948 for refspec in negative {
6949 if refspec_matches_source(refspec, source)? {
6950 return Ok(true);
6951 }
6952 }
6953 Ok(false)
6954}
6955
6956fn zero_object_id(format: ObjectFormat) -> Result<ObjectId> {
6957 Ok(ObjectId::null(format))
6958}
6959
6960fn local_ref<'a>(refs: &'a [PushSourceRef], name: &str) -> Option<&'a PushSourceRef> {
6961 refs.iter().find(|reference| reference.name == name)
6962}
6963
6964fn remote_ref<'a>(refs: &'a [RefAdvertisement], name: &str) -> Option<&'a RefAdvertisement> {
6965 refs.iter().find(|reference| reference.name == name)
6966}
6967
6968fn validate_push_source_ref(format: ObjectFormat, reference: &PushSourceRef) -> Result<()> {
6969 if reference.oid.format() != format {
6970 return Err(GitError::InvalidObjectId(
6971 "push source ref object format does not match repository".into(),
6972 ));
6973 }
6974 validate_refspec_endpoint("push source ref name", &reference.name)
6975}
6976
6977fn require_receive_pack_feature(advertised: bool, name: &str) -> Result<()> {
6978 if advertised {
6979 Ok(())
6980 } else {
6981 Err(GitError::InvalidFormat(format!(
6982 "receive-pack feature {name} was not advertised"
6983 )))
6984 }
6985}
6986
6987fn validate_smart_http_service(service: GitService) -> Result<()> {
6988 match service {
6989 GitService::UploadPack | GitService::ReceivePack => Ok(()),
6990 GitService::UploadArchive => Err(GitError::InvalidFormat(
6991 "smart HTTP only supports upload-pack and receive-pack services".into(),
6992 )),
6993 }
6994}
6995
6996fn normalize_http_repository_path(path: &str) -> Result<String> {
6997 if path.is_empty() {
6998 return Err(GitError::InvalidFormat(
6999 "smart HTTP repository path is empty".into(),
7000 ));
7001 }
7002 if !path.starts_with('/') {
7003 return Err(GitError::InvalidFormat(
7004 "smart HTTP repository path must start with /".into(),
7005 ));
7006 }
7007 if path
7008 .bytes()
7009 .any(|byte| matches!(byte, b'\n' | b'\r' | 0 | b'?' | b'#'))
7010 {
7011 return Err(GitError::InvalidFormat(
7012 "smart HTTP repository path contains a delimiter byte".into(),
7013 ));
7014 }
7015 let normalized = path.trim_end_matches('/');
7016 Ok(if normalized.is_empty() {
7017 "/".into()
7018 } else {
7019 normalized.to_string()
7020 })
7021}
7022
7023fn dumb_http_pack_resource_path(
7024 repository_path: &str,
7025 hash: &ObjectId,
7026 suffix: &str,
7027) -> Result<String> {
7028 let repository_path = normalize_http_repository_path(repository_path)?;
7029 Ok(format!(
7030 "{repository_path}/objects/pack/pack-{hash}.{suffix}"
7031 ))
7032}
7033
7034fn parse_smart_http_content_type(value: &str, suffix: &str) -> Result<GitService> {
7035 let value = value.trim();
7036 if value.is_empty() {
7037 return Err(GitError::InvalidFormat(
7038 "smart HTTP content type is empty".into(),
7039 ));
7040 }
7041 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7042 return Err(GitError::InvalidFormat(
7043 "smart HTTP content type contains a delimiter byte".into(),
7044 ));
7045 }
7046 let value = value.to_ascii_lowercase();
7047 let service = value
7048 .strip_prefix("application/x-")
7049 .and_then(|value| value.strip_suffix(suffix))
7050 .ok_or_else(|| GitError::InvalidFormat("invalid smart HTTP content type".into()))?;
7051 let service = parse_git_service(service)?;
7052 validate_smart_http_service(service)?;
7053 Ok(service)
7054}
7055
7056fn validate_dumb_http_info_ref_line(value: &[u8]) -> Result<()> {
7057 if value.is_empty() {
7058 return Err(GitError::InvalidFormat(
7059 "dumb HTTP ref record is empty".into(),
7060 ));
7061 }
7062 if !value.ends_with(b"\n") {
7063 return Err(GitError::InvalidFormat(
7064 "dumb HTTP ref record missing LF".into(),
7065 ));
7066 }
7067 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7068 return Err(GitError::InvalidFormat(
7069 "dumb HTTP ref record contains a delimiter byte".into(),
7070 ));
7071 }
7072 Ok(())
7073}
7074
7075fn validate_dumb_http_ref_name(value: &str) -> Result<()> {
7076 validate_protocol_v2_token("dumb HTTP ref name", value)?;
7077 if value.ends_with("^{}") {
7078 return Err(GitError::InvalidFormat(
7079 "dumb HTTP ref name must not include peeled suffix".into(),
7080 ));
7081 }
7082 Ok(())
7083}
7084
7085fn validate_dumb_http_alternate_line(value: &[u8]) -> Result<()> {
7086 if value.is_empty() {
7087 return Err(GitError::InvalidFormat(
7088 "dumb HTTP alternate is empty".into(),
7089 ));
7090 }
7091 if !value.ends_with(b"\n") {
7092 return Err(GitError::InvalidFormat(
7093 "dumb HTTP alternate missing LF".into(),
7094 ));
7095 }
7096 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7097 return Err(GitError::InvalidFormat(
7098 "dumb HTTP alternate contains a delimiter byte".into(),
7099 ));
7100 }
7101 Ok(())
7102}
7103
7104fn validate_dumb_http_alternate(value: &str) -> Result<()> {
7105 if value.is_empty() {
7106 return Err(GitError::InvalidFormat(
7107 "dumb HTTP alternate is empty".into(),
7108 ));
7109 }
7110 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7111 return Err(GitError::InvalidFormat(
7112 "dumb HTTP alternate contains a delimiter byte".into(),
7113 ));
7114 }
7115 Ok(())
7116}
7117
7118fn validate_protocol_v2_token(label: &str, value: &str) -> Result<()> {
7119 if value.is_empty() {
7120 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7121 }
7122 if value
7123 .bytes()
7124 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | 0))
7125 {
7126 return Err(GitError::InvalidFormat(format!(
7127 "{label} contains a delimiter byte"
7128 )));
7129 }
7130 Ok(())
7131}
7132
7133fn validate_protocol_v2_line(label: &str, value: &[u8]) -> Result<()> {
7134 if value.is_empty() {
7135 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7136 }
7137 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7138 return Err(GitError::InvalidFormat(format!(
7139 "{label} contains a delimiter byte"
7140 )));
7141 }
7142 Ok(())
7143}
7144
7145fn parse_protocol_v2_line_text<'a>(label: &str, value: &'a [u8]) -> Result<&'a str> {
7146 validate_protocol_v2_line(label, value)?;
7147 let value = trim_trailing_lf(value);
7148 if value.is_empty() {
7149 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7150 }
7151 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
7152 return Err(GitError::InvalidFormat(format!(
7153 "{label} contains a delimiter byte"
7154 )));
7155 }
7156 std::str::from_utf8(value).map_err(|err| GitError::InvalidFormat(err.to_string()))
7157}
7158
7159fn parse_oid_argument(
7160 format: ObjectFormat,
7161 label: &str,
7162 value: &str,
7163 prefix: &str,
7164) -> Result<ObjectId> {
7165 let oid = value
7166 .strip_prefix(prefix)
7167 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7168 validate_protocol_v2_token(label, oid)?;
7169 ObjectId::from_hex(format, oid)
7170}
7171
7172fn parse_u32_argument(label: &str, value: &str, prefix: &str) -> Result<u32> {
7173 let number = value
7174 .strip_prefix(prefix)
7175 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7176 validate_protocol_v2_token(label, number)?;
7177 let parsed = number
7178 .parse::<u32>()
7179 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7180 if parsed == 0 {
7181 return Err(GitError::InvalidFormat(format!("{label} must be positive")));
7182 }
7183 Ok(parsed)
7184}
7185
7186fn parse_u64_argument(label: &str, value: &str, prefix: &str) -> Result<u64> {
7187 let number = value
7188 .strip_prefix(prefix)
7189 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7190 validate_protocol_v2_token(label, number)?;
7191 number
7192 .parse::<u64>()
7193 .map_err(|err| GitError::InvalidFormat(err.to_string()))
7194}
7195
7196fn line(mut payload: Vec<u8>) -> Vec<u8> {
7197 payload.push(b'\n');
7198 payload
7199}
7200
7201fn line_from_str(payload: &str) -> Vec<u8> {
7202 line(payload.as_bytes().to_vec())
7203}
7204
7205fn trim_trailing_lf(input: &[u8]) -> &[u8] {
7206 input.strip_suffix(b"\n").unwrap_or(input)
7207}
7208
7209#[cfg(test)]
7210mod tests {
7211 use super::*;
7212
7213 #[test]
7214 fn pkt_line_frame_encodes_data_and_control_frames() {
7215 assert_eq!(PktLine(b"hello\n".to_vec()).encode(), b"000ahello\n");
7216 assert_eq!(
7217 PktLineFrame::data(b"hello\n".to_vec())
7218 .expect("test operation should succeed")
7219 .encode(),
7220 b"000ahello\n"
7221 );
7222 assert_eq!(PktLineFrame::Flush.encode(), b"0000");
7223 assert_eq!(PktLineFrame::Delimiter.encode(), b"0001");
7224 assert_eq!(PktLineFrame::ResponseEnd.encode(), b"0002");
7225 assert_eq!(
7226 PktLineFrame::data(b"hello\n".to_vec())
7227 .expect("test operation should succeed")
7228 .try_encode()
7229 .expect("test operation should succeed"),
7230 b"000ahello\n"
7231 );
7232 }
7233
7234 #[test]
7235 fn pkt_line_frame_parses_data_and_control_frames() {
7236 assert_eq!(
7237 PktLineFrame::parse(b"000ahello\n").expect("test operation should succeed"),
7238 (PktLineFrame::Data(b"hello\n".to_vec()), 10)
7239 );
7240 assert_eq!(
7241 PktLineFrame::parse(b"0000").expect("test operation should succeed"),
7242 (PktLineFrame::Flush, 4)
7243 );
7244 assert_eq!(
7245 PktLineFrame::parse(b"0001").expect("test operation should succeed"),
7246 (PktLineFrame::Delimiter, 4)
7247 );
7248 assert_eq!(
7249 PktLineFrame::parse(b"0002").expect("test operation should succeed"),
7250 (PktLineFrame::ResponseEnd, 4)
7251 );
7252 }
7253
7254 #[test]
7255 fn pkt_line_stream_parses_multiple_frames() {
7256 let frames = parse_pkt_line_stream(b"000eversion 2\n00010009done\n0000")
7257 .expect("test operation should succeed");
7258 assert_eq!(
7259 frames,
7260 vec![
7261 PktLineFrame::Data(b"version 2\n".to_vec()),
7262 PktLineFrame::Delimiter,
7263 PktLineFrame::Data(b"done\n".to_vec()),
7264 PktLineFrame::Flush,
7265 ]
7266 );
7267 }
7268
7269 #[test]
7270 fn pkt_line_stream_reads_and_writes_incremental_io() {
7271 let frames = vec![
7272 PktLineFrame::Data(b"version 2\n".to_vec()),
7273 PktLineFrame::Delimiter,
7274 PktLineFrame::Data(b"done\n".to_vec()),
7275 PktLineFrame::Flush,
7276 ];
7277 let mut encoded = Vec::new();
7278 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
7279 assert_eq!(encoded, b"000eversion 2\n00010009done\n0000");
7280 assert_eq!(
7281 read_pkt_line_frames(&mut encoded.as_slice()).expect("test operation should succeed"),
7282 frames
7283 );
7284
7285 let mut empty: &[u8] = b"";
7286 assert_eq!(
7287 read_pkt_line_frame(&mut empty).expect("test operation should succeed"),
7288 None
7289 );
7290 }
7291
7292 #[test]
7293 fn pkt_line_stream_reads_until_control_packets() {
7294 let input = b"000eversion 2\n0000trailing";
7295 let frames = read_pkt_line_frames_until_flush(&mut &input[..])
7296 .expect("test operation should succeed");
7297 assert_eq!(
7298 frames,
7299 vec![
7300 PktLineFrame::Data(b"version 2\n".to_vec()),
7301 PktLineFrame::Flush,
7302 ]
7303 );
7304
7305 let input = b"0009done\n0002next";
7306 let frames = read_pkt_line_frames_until_response_end(&mut &input[..])
7307 .expect("test operation should succeed");
7308 assert_eq!(
7309 frames,
7310 vec![
7311 PktLineFrame::Data(b"done\n".to_vec()),
7312 PktLineFrame::ResponseEnd,
7313 ]
7314 );
7315 assert!(read_pkt_line_frames_until_flush(&mut &b"0009done\n"[..]).is_err());
7316 }
7317
7318 #[test]
7319 fn pkt_line_rejects_invalid_lengths() {
7320 assert!(PktLineFrame::parse(b"000").is_err());
7321 assert!(PktLineFrame::parse(b"0003").is_err());
7322 assert!(PktLineFrame::parse(b"000ahello").is_err());
7323 assert!(PktLineFrame::parse(b"zzzz").is_err());
7324 assert!(read_pkt_line_frame(&mut &b"000"[..]).is_err());
7325 assert!(read_pkt_line_frame(&mut &b"0003"[..]).is_err());
7326 }
7327
7328 #[test]
7329 fn pkt_line_rejects_oversized_data() {
7330 let payload = vec![b'x'; PKT_LINE_MAX_PAYLOAD_LEN + 1];
7331 assert!(PktLineFrame::data(payload.clone()).is_err());
7332 assert!(PktLine(payload.clone()).try_encode().is_err());
7333 assert!(PktLineFrame::Data(payload.clone()).try_encode().is_err());
7334 assert!(write_pkt_line_frame(&mut Vec::new(), &PktLineFrame::Data(payload)).is_err());
7335 assert!(PktLineFrame::parse(b"fff1").is_err());
7336 }
7337
7338 #[test]
7339 fn protocol_error_lines_parse_encode_and_stream() {
7340 let error = parse_error_line(b"ERR remote rejected request\n")
7341 .expect("test operation should succeed");
7342 assert_eq!(
7343 error,
7344 ProtocolErrorLine {
7345 message: "remote rejected request".into(),
7346 }
7347 );
7348 assert_eq!(
7349 encode_error_line(&error).expect("test operation should succeed"),
7350 b"ERR remote rejected request\n"
7351 );
7352 assert_eq!(
7353 parse_error_frame(&PktLineFrame::Data(
7354 b"ERR remote rejected request\n".to_vec()
7355 ))
7356 .expect("test operation should succeed"),
7357 Some(error.clone())
7358 );
7359 assert_eq!(
7360 parse_error_frame(&PktLineFrame::Data(b"NAK\n".to_vec()))
7361 .expect("test operation should succeed"),
7362 None
7363 );
7364
7365 let mut encoded = Vec::new();
7366 write_error_line(&mut encoded, &error).expect("test operation should succeed");
7367 encoded.extend_from_slice(b"tail");
7368 let mut input = encoded.as_slice();
7369 assert_eq!(
7370 read_error_line(&mut input).expect("test operation should succeed"),
7371 error
7372 );
7373 assert_eq!(input, b"tail");
7374 }
7375
7376 #[test]
7377 fn protocol_error_lines_reject_malformed_messages() {
7378 assert!(parse_error_line(b"ERR\n").is_err());
7379 assert!(parse_error_line(b"ERR \n").is_err());
7380 assert!(parse_error_line(b"ERR bad\0message\n").is_err());
7381 assert!(parse_error_line(b"NAK\n").is_err());
7382 assert!(
7383 encode_error_line(&ProtocolErrorLine {
7384 message: "bad\nmessage".into(),
7385 })
7386 .is_err()
7387 );
7388 assert!(read_error_line(&mut &b"0000"[..]).is_err());
7389 }
7390
7391 #[test]
7392 fn refspec_parser_handles_fetch_push_and_negative_forms() {
7393 assert_eq!(
7394 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7395 .expect("test operation should succeed"),
7396 RefSpec {
7397 force: true,
7398 negative: false,
7399 src: Some("refs/heads/*".into()),
7400 dst: Some("refs/remotes/origin/*".into()),
7401 pattern: true,
7402 }
7403 );
7404 assert_eq!(
7405 parse_refspec("refs/heads/main").expect("test operation should succeed"),
7406 RefSpec {
7407 force: false,
7408 negative: false,
7409 src: Some("refs/heads/main".into()),
7410 dst: None,
7411 pattern: false,
7412 }
7413 );
7414 assert_eq!(
7415 parse_refspec(":refs/heads/topic").expect("test operation should succeed"),
7416 RefSpec {
7417 force: false,
7418 negative: false,
7419 src: None,
7420 dst: Some("refs/heads/topic".into()),
7421 pattern: false,
7422 }
7423 );
7424 assert_eq!(
7425 parse_refspec(":").expect("test operation should succeed"),
7426 RefSpec {
7427 force: false,
7428 negative: false,
7429 src: None,
7430 dst: None,
7431 pattern: false,
7432 }
7433 );
7434 assert_eq!(
7435 parse_refspec("^refs/tags/private/*").expect("test operation should succeed"),
7436 RefSpec {
7437 force: false,
7438 negative: true,
7439 src: Some("refs/tags/private/*".into()),
7440 dst: None,
7441 pattern: true,
7442 }
7443 );
7444 }
7445
7446 #[test]
7447 fn refspec_encode_and_map_sources() {
7448 let pattern = parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7449 .expect("test operation should succeed");
7450 assert_eq!(
7451 encode_refspec(&pattern).expect("test operation should succeed"),
7452 "+refs/heads/*:refs/remotes/origin/*"
7453 );
7454 assert!(
7455 refspec_matches_source(&pattern, "refs/heads/main")
7456 .expect("test operation should succeed")
7457 );
7458 assert_eq!(
7459 refspec_map_source(&pattern, "refs/heads/main").expect("test operation should succeed"),
7460 Some("refs/remotes/origin/main".into())
7461 );
7462 assert_eq!(
7463 refspec_map_source(&pattern, "refs/tags/v1").expect("test operation should succeed"),
7464 None
7465 );
7466
7467 let direct = parse_refspec("HEAD:refs/heads/main").expect("test operation should succeed");
7468 assert_eq!(
7469 encode_refspec(&direct).expect("test operation should succeed"),
7470 "HEAD:refs/heads/main"
7471 );
7472 assert_eq!(
7473 refspec_map_source(&direct, "HEAD").expect("test operation should succeed"),
7474 Some("refs/heads/main".into())
7475 );
7476
7477 let delete = parse_refspec(":refs/heads/old").expect("test operation should succeed");
7478 assert_eq!(
7479 encode_refspec(&delete).expect("test operation should succeed"),
7480 ":refs/heads/old"
7481 );
7482 assert_eq!(
7483 refspec_map_source(&delete, "HEAD").expect("test operation should succeed"),
7484 None
7485 );
7486
7487 let matching = parse_refspec(":").expect("test operation should succeed");
7488 assert_eq!(
7489 encode_refspec(&matching).expect("test operation should succeed"),
7490 ":"
7491 );
7492 }
7493
7494 #[test]
7495 fn refspec_parser_rejects_malformed_values() {
7496 assert!(parse_refspec("").is_err());
7497 assert!(parse_refspec("+^refs/heads/main").is_err());
7498 assert!(parse_refspec("^refs/heads/main:refs/remotes/origin/main").is_err());
7499 assert!(parse_refspec("refs/heads/*:refs/remotes/origin/main").is_err());
7500 assert!(parse_refspec("refs/heads/**:refs/remotes/origin/*").is_err());
7501 assert!(parse_refspec("refs/heads/main:refs/remotes/origin/main:extra").is_err());
7502 assert!(parse_refspec("refs/heads/main\n").is_err());
7503 assert!(
7504 encode_refspec(&RefSpec {
7505 force: false,
7506 negative: false,
7507 src: Some("refs/heads/*".into()),
7508 dst: Some("refs/remotes/origin/main".into()),
7509 pattern: true,
7510 })
7511 .is_err()
7512 );
7513 }
7514
7515 #[test]
7516 fn fetch_head_records_parse_encode_and_describe_refs() {
7517 let first = ObjectId::from_hex(
7518 ObjectFormat::Sha1,
7519 "1111111111111111111111111111111111111111",
7520 )
7521 .expect("test operation should succeed");
7522 let second = ObjectId::from_hex(
7523 ObjectFormat::Sha1,
7524 "2222222222222222222222222222222222222222",
7525 )
7526 .expect("test operation should succeed");
7527 let input = b"1111111111111111111111111111111111111111\t\tbranch 'main' of ../bundle.bdl\n2222222222222222222222222222222222222222\tnot-for-merge\ttag 'v1' of ../bundle.bdl\n";
7528 let records =
7529 parse_fetch_head(ObjectFormat::Sha1, input).expect("test operation should succeed");
7530 assert_eq!(
7531 records,
7532 vec![
7533 FetchHeadRecord {
7534 oid: first,
7535 not_for_merge: false,
7536 description: "branch 'main' of ../bundle.bdl".into(),
7537 },
7538 FetchHeadRecord {
7539 oid: second,
7540 not_for_merge: true,
7541 description: "tag 'v1' of ../bundle.bdl".into(),
7542 },
7543 ]
7544 );
7545 assert_eq!(
7546 encode_fetch_head(&records).expect("test operation should succeed"),
7547 input
7548 );
7549 assert_eq!(
7550 parse_fetch_head(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
7551 Vec::<FetchHeadRecord>::new()
7552 );
7553 assert_eq!(
7554 fetch_head_remote_description("refs/heads/main", "../bundle.bdl")
7555 .expect("test operation should succeed"),
7556 "branch 'main' of ../bundle.bdl"
7557 );
7558 assert_eq!(
7559 fetch_head_remote_description("refs/tags/v1", "../bundle.bdl")
7560 .expect("test operation should succeed"),
7561 "tag 'v1' of ../bundle.bdl"
7562 );
7563 assert_eq!(
7565 fetch_head_remote_description("HEAD", "../bundle.bdl")
7566 .expect("test operation should succeed"),
7567 "../bundle.bdl"
7568 );
7569 }
7570
7571 #[test]
7572 fn fetch_head_records_streams_round_trip() {
7573 let records = vec![FetchHeadRecord {
7574 oid: ObjectId::from_hex(
7575 ObjectFormat::Sha1,
7576 "1111111111111111111111111111111111111111",
7577 )
7578 .expect("test operation should succeed"),
7579 not_for_merge: false,
7580 description: "branch 'main' of ../bundle.bdl".into(),
7581 }];
7582 let mut encoded = Vec::new();
7583 write_fetch_head(&mut encoded, &records).expect("test operation should succeed");
7584 let mut input = encoded.as_slice();
7585 assert_eq!(
7586 read_fetch_head(ObjectFormat::Sha1, &mut input).expect("test operation should succeed"),
7587 records
7588 );
7589 assert!(input.is_empty());
7590 }
7591
7592 #[test]
7593 fn fetch_head_records_reject_malformed_lines() {
7594 assert!(
7595 parse_fetch_head(
7596 ObjectFormat::Sha1,
7597 b"1111111111111111111111111111111111111111\t\tbranch 'main'"
7598 )
7599 .is_err()
7600 );
7601 assert!(
7602 parse_fetch_head(
7603 ObjectFormat::Sha1,
7604 b"1111111111111111111111111111111111111111\tfor-merge\tbranch 'main'\n"
7605 )
7606 .is_err()
7607 );
7608 assert!(parse_fetch_head(ObjectFormat::Sha1, b"not-a-hash\t\tbranch 'main'\n").is_err());
7609 assert!(
7610 encode_fetch_head(&[FetchHeadRecord {
7611 oid: ObjectId::from_hex(
7612 ObjectFormat::Sha1,
7613 "1111111111111111111111111111111111111111"
7614 )
7615 .expect("test operation should succeed"),
7616 not_for_merge: false,
7617 description: "bad\ndescription".into(),
7618 }])
7619 .is_err()
7620 );
7621 }
7622
7623 #[test]
7624 fn fetch_planner_maps_direct_pattern_and_negative_refspecs() {
7625 let main = ObjectId::from_hex(
7626 ObjectFormat::Sha1,
7627 "1111111111111111111111111111111111111111",
7628 )
7629 .expect("test operation should succeed");
7630 let next = ObjectId::from_hex(
7631 ObjectFormat::Sha1,
7632 "2222222222222222222222222222222222222222",
7633 )
7634 .expect("test operation should succeed");
7635 let refs = vec![
7636 RefAdvertisement {
7637 oid: main.clone(),
7638 name: "refs/heads/main".into(),
7639 capabilities: Vec::new(),
7640 },
7641 RefAdvertisement {
7642 oid: next.clone(),
7643 name: "refs/heads/tmp".into(),
7644 capabilities: Vec::new(),
7645 },
7646 ];
7647 let refspecs = vec![
7648 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7649 .expect("test operation should succeed"),
7650 parse_refspec("^refs/heads/tmp").expect("test operation should succeed"),
7651 ];
7652 assert_eq!(
7653 plan_fetch_ref_updates(&refs, &refspecs, false).expect("test operation should succeed"),
7654 vec![FetchRefUpdate {
7655 src: "refs/heads/main".into(),
7656 dst: Some("refs/remotes/origin/main".into()),
7657 oid: main,
7658 not_for_merge: false,
7659 }]
7660 );
7661 }
7662
7663 #[test]
7664 fn fetch_planner_autofollows_tags_and_builds_fetch_head_records() {
7665 let commit = ObjectId::from_hex(
7666 ObjectFormat::Sha1,
7667 "1111111111111111111111111111111111111111",
7668 )
7669 .expect("test operation should succeed");
7670 let refs = vec![
7671 RefAdvertisement {
7672 oid: commit.clone(),
7673 name: "refs/heads/main".into(),
7674 capabilities: Vec::new(),
7675 },
7676 RefAdvertisement {
7677 oid: commit.clone(),
7678 name: "refs/tags/v1".into(),
7679 capabilities: Vec::new(),
7680 },
7681 ];
7682 let refspecs = vec![
7683 parse_refspec("refs/heads/main:refs/heads/main")
7684 .expect("test operation should succeed"),
7685 ];
7686 let updates =
7687 plan_fetch_ref_updates(&refs, &refspecs, true).expect("test operation should succeed");
7688 assert_eq!(
7689 updates,
7690 vec![
7691 FetchRefUpdate {
7692 src: "refs/heads/main".into(),
7693 dst: Some("refs/heads/main".into()),
7694 oid: commit.clone(),
7695 not_for_merge: false,
7696 },
7697 FetchRefUpdate {
7698 src: "refs/tags/v1".into(),
7699 dst: Some("refs/tags/v1".into()),
7700 oid: commit.clone(),
7701 not_for_merge: true,
7702 },
7703 ]
7704 );
7705 assert_eq!(
7706 fetch_ref_updates_to_fetch_head(&updates, "../bundle.bdl")
7707 .expect("test operation should succeed"),
7708 vec![
7709 FetchHeadRecord {
7710 oid: commit.clone(),
7711 not_for_merge: false,
7712 description: "branch 'main' of ../bundle.bdl".into(),
7713 },
7714 FetchHeadRecord {
7715 oid: commit,
7716 not_for_merge: true,
7717 description: "tag 'v1' of ../bundle.bdl".into(),
7718 },
7719 ]
7720 );
7721 }
7722
7723 #[test]
7724 fn fetch_planner_rejects_missing_or_sourceless_refspecs() {
7725 let refs = vec![RefAdvertisement {
7726 oid: ObjectId::from_hex(
7727 ObjectFormat::Sha1,
7728 "1111111111111111111111111111111111111111",
7729 )
7730 .expect("test operation should succeed"),
7731 name: "refs/heads/main".into(),
7732 capabilities: Vec::new(),
7733 }];
7734 assert!(
7735 plan_fetch_ref_updates(
7736 &refs,
7737 &[parse_refspec("refs/heads/missing").expect("test operation should succeed")],
7738 false
7739 )
7740 .is_err()
7741 );
7742 assert!(
7743 plan_fetch_ref_updates(
7744 &refs,
7745 &[parse_refspec(":refs/heads/main").expect("test operation should succeed")],
7746 false
7747 )
7748 .is_err()
7749 );
7750 }
7751
7752 #[test]
7753 fn fetch_planner_sourceless_positive_refspec_returns_err_not_panic() {
7754 let refs = vec![RefAdvertisement {
7759 oid: ObjectId::from_hex(
7760 ObjectFormat::Sha1,
7761 "1111111111111111111111111111111111111111",
7762 )
7763 .expect("test operation should succeed"),
7764 name: "refs/heads/main".into(),
7765 capabilities: Vec::new(),
7766 }];
7767 let malformed = RefSpec {
7768 force: false,
7769 negative: false,
7770 src: None,
7771 dst: Some("refs/heads/main".into()),
7772 pattern: false,
7773 };
7774 let result = plan_fetch_ref_updates(&refs, &[malformed], false);
7775 assert!(
7776 result.is_err(),
7777 "sourceless positive refspec must yield Err, got {result:?}"
7778 );
7779 }
7780
7781 #[test]
7782 fn push_planner_builds_create_update_delete_and_matching_commands() {
7783 let old = ObjectId::from_hex(
7784 ObjectFormat::Sha1,
7785 "1111111111111111111111111111111111111111",
7786 )
7787 .expect("test operation should succeed");
7788 let new = ObjectId::from_hex(
7789 ObjectFormat::Sha1,
7790 "2222222222222222222222222222222222222222",
7791 )
7792 .expect("test operation should succeed");
7793 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
7794 let local_refs = vec![
7795 PushSourceRef {
7796 oid: new.clone(),
7797 name: "refs/heads/main".into(),
7798 },
7799 PushSourceRef {
7800 oid: new.clone(),
7801 name: "refs/heads/new".into(),
7802 },
7803 ];
7804 let remote_refs = vec![
7805 RefAdvertisement {
7806 oid: old.clone(),
7807 name: "refs/heads/main".into(),
7808 capabilities: Vec::new(),
7809 },
7810 RefAdvertisement {
7811 oid: old.clone(),
7812 name: "refs/heads/old".into(),
7813 capabilities: Vec::new(),
7814 },
7815 ];
7816
7817 assert_eq!(
7818 plan_push_commands(
7819 ObjectFormat::Sha1,
7820 &local_refs,
7821 &remote_refs,
7822 &[parse_refspec("refs/heads/main").expect("test operation should succeed")],
7823 )
7824 .expect("test operation should succeed"),
7825 vec![ReceivePackCommand {
7826 old_id: old.clone(),
7827 new_id: new.clone(),
7828 name: "refs/heads/main".into(),
7829 }]
7830 );
7831 assert_eq!(
7832 plan_push_commands(
7833 ObjectFormat::Sha1,
7834 &local_refs,
7835 &remote_refs,
7836 &[parse_refspec("refs/heads/new:refs/heads/new")
7837 .expect("test operation should succeed")],
7838 )
7839 .expect("test operation should succeed"),
7840 vec![ReceivePackCommand {
7841 old_id: zero.clone(),
7842 new_id: new.clone(),
7843 name: "refs/heads/new".into(),
7844 }]
7845 );
7846 assert_eq!(
7847 plan_push_commands(
7848 ObjectFormat::Sha1,
7849 &local_refs,
7850 &remote_refs,
7851 &[parse_refspec(":refs/heads/old").expect("test operation should succeed")],
7852 )
7853 .expect("test operation should succeed"),
7854 vec![ReceivePackCommand {
7855 old_id: old.clone(),
7856 new_id: zero,
7857 name: "refs/heads/old".into(),
7858 }]
7859 );
7860 assert_eq!(
7861 plan_push_commands(
7862 ObjectFormat::Sha1,
7863 &local_refs,
7864 &remote_refs,
7865 &[parse_refspec(":").expect("test operation should succeed")],
7866 )
7867 .expect("test operation should succeed"),
7868 vec![ReceivePackCommand {
7869 old_id: old,
7870 new_id: new,
7871 name: "refs/heads/main".into(),
7872 }]
7873 );
7874 }
7875
7876 #[test]
7877 fn push_planner_builds_wildcard_commands_and_rejects_bad_refspecs() {
7878 let new = ObjectId::from_hex(
7879 ObjectFormat::Sha1,
7880 "2222222222222222222222222222222222222222",
7881 )
7882 .expect("test operation should succeed");
7883 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
7884 let local_refs = vec![PushSourceRef {
7885 oid: new.clone(),
7886 name: "refs/heads/topic".into(),
7887 }];
7888 let commands = plan_push_commands(
7889 ObjectFormat::Sha1,
7890 &local_refs,
7891 &[],
7892 &[parse_refspec("refs/heads/*:refs/heads/review/*")
7893 .expect("test operation should succeed")],
7894 )
7895 .expect("test operation should succeed");
7896 assert_eq!(
7897 commands,
7898 vec![ReceivePackCommand {
7899 old_id: zero,
7900 new_id: new,
7901 name: "refs/heads/review/topic".into(),
7902 }]
7903 );
7904 assert!(
7905 plan_push_commands(
7906 ObjectFormat::Sha1,
7907 &local_refs,
7908 &[],
7909 &[parse_refspec("^refs/heads/topic").expect("test operation should succeed")],
7910 )
7911 .is_err()
7912 );
7913 assert!(
7914 plan_push_commands(
7915 ObjectFormat::Sha1,
7916 &local_refs,
7917 &[],
7918 &[parse_refspec(":refs/heads/missing").expect("test operation should succeed")],
7919 )
7920 .is_err()
7921 );
7922 }
7923
7924 #[test]
7925 fn receive_pack_push_request_builder_negotiates_capabilities() {
7926 let old_id = ObjectId::from_hex(
7927 ObjectFormat::Sha1,
7928 "1111111111111111111111111111111111111111",
7929 )
7930 .expect("test operation should succeed");
7931 let new_id = ObjectId::from_hex(
7932 ObjectFormat::Sha1,
7933 "2222222222222222222222222222222222222222",
7934 )
7935 .expect("test operation should succeed");
7936 let features = ReceivePackFeatures {
7937 report_status_v2: true,
7938 atomic: true,
7939 ofs_delta: true,
7940 push_options: true,
7941 side_band_64k: true,
7942 quiet: true,
7943 object_format: Some(ObjectFormat::Sha1),
7944 ..ReceivePackFeatures::default()
7945 };
7946 let request = build_receive_pack_push_request(
7947 &features,
7948 vec![ReceivePackCommand {
7949 old_id,
7950 new_id,
7951 name: "refs/heads/main".into(),
7952 }],
7953 b"PACKdata".to_vec(),
7954 ReceivePackPushRequestOptions {
7955 report_status_v2: true,
7956 atomic: true,
7957 ofs_delta: true,
7958 side_band_64k: true,
7959 quiet: true,
7960 agent: Some("sley/0".into()),
7961 object_format: Some(ObjectFormat::Sha1),
7962 push_options: vec!["ci.skip".into()],
7963 ..ReceivePackPushRequestOptions::default()
7964 },
7965 )
7966 .expect("test operation should succeed");
7967 assert_eq!(
7968 request.commands.capabilities,
7969 vec![
7970 Capability {
7971 name: "report-status-v2".into(),
7972 value: None,
7973 },
7974 Capability {
7975 name: "atomic".into(),
7976 value: None,
7977 },
7978 Capability {
7979 name: "ofs-delta".into(),
7980 value: None,
7981 },
7982 Capability {
7983 name: "side-band-64k".into(),
7984 value: None,
7985 },
7986 Capability {
7987 name: "quiet".into(),
7988 value: None,
7989 },
7990 Capability {
7991 name: "agent".into(),
7992 value: Some("sley/0".into()),
7993 },
7994 Capability {
7995 name: "object-format".into(),
7996 value: Some("sha1".into()),
7997 },
7998 Capability {
7999 name: "push-options".into(),
8000 value: None,
8001 },
8002 ]
8003 );
8004 assert_eq!(request.push_options, Some(vec!["ci.skip".into()]));
8005 validate_receive_pack_push_request_features(&features, &request)
8006 .expect("test operation should succeed");
8007 }
8008
8009 #[test]
8010 fn receive_pack_push_request_builder_handles_delete_only_and_rejects_unadvertised() {
8011 let old_id = ObjectId::from_hex(
8012 ObjectFormat::Sha1,
8013 "1111111111111111111111111111111111111111",
8014 )
8015 .expect("test operation should succeed");
8016 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8017 let features = ReceivePackFeatures {
8018 delete_refs: true,
8019 ..ReceivePackFeatures::default()
8020 };
8021 let request = build_receive_pack_push_request(
8022 &features,
8023 vec![ReceivePackCommand {
8024 old_id,
8025 new_id: zero,
8026 name: "refs/heads/old".into(),
8027 }],
8028 Vec::new(),
8029 ReceivePackPushRequestOptions::default(),
8030 )
8031 .expect("test operation should succeed");
8032 assert_eq!(
8033 request.commands.capabilities,
8034 vec![Capability {
8035 name: "delete-refs".into(),
8036 value: None,
8037 }]
8038 );
8039 assert!(request.packfile.is_empty());
8040
8041 assert!(
8042 build_receive_pack_push_request(
8043 &ReceivePackFeatures::default(),
8044 request.commands.commands.clone(),
8045 Vec::new(),
8046 ReceivePackPushRequestOptions::default(),
8047 )
8048 .is_err()
8049 );
8050 assert!(
8051 build_receive_pack_push_request(
8052 &features,
8053 request.commands.commands,
8054 b"PACK".to_vec(),
8055 ReceivePackPushRequestOptions::default(),
8056 )
8057 .is_err()
8058 );
8059 assert!(
8060 build_receive_pack_push_request(
8061 &features,
8062 Vec::new(),
8063 Vec::new(),
8064 ReceivePackPushRequestOptions {
8065 push_options: vec!["ci.skip".into()],
8066 ..ReceivePackPushRequestOptions::default()
8067 },
8068 )
8069 .is_err()
8070 );
8071 }
8072
8073 #[test]
8074 fn smart_http_helpers_build_paths_and_content_types() {
8075 let sha1 = ObjectId::from_hex(
8076 ObjectFormat::Sha1,
8077 "1111111111111111111111111111111111111111",
8078 )
8079 .expect("test operation should succeed");
8080 let sha256 = ObjectId::from_hex(
8081 ObjectFormat::Sha256,
8082 "2222222222222222222222222222222222222222222222222222222222222222",
8083 )
8084 .expect("test operation should succeed");
8085 assert_eq!(
8086 smart_http_info_refs_path("/repo.git/", GitService::UploadPack)
8087 .expect("test operation should succeed"),
8088 "/repo.git/info/refs?service=git-upload-pack"
8089 );
8090 assert_eq!(
8091 dumb_http_info_refs_path("/repo.git/").expect("test operation should succeed"),
8092 "/repo.git/info/refs"
8093 );
8094 assert_eq!(
8095 dumb_http_alternates_path("/repo.git").expect("test operation should succeed"),
8096 "/repo.git/objects/info/http-alternates"
8097 );
8098 assert_eq!(
8099 dumb_http_packs_path("/repo.git/").expect("test operation should succeed"),
8100 "/repo.git/objects/info/packs"
8101 );
8102 assert_eq!(
8103 dumb_http_loose_object_path("/repo.git/", &sha1)
8104 .expect("test operation should succeed"),
8105 "/repo.git/objects/11/11111111111111111111111111111111111111"
8106 );
8107 assert_eq!(
8108 dumb_http_loose_object_path("/repo.git/", &sha256)
8109 .expect("test operation should succeed"),
8110 "/repo.git/objects/22/22222222222222222222222222222222222222222222222222222222222222"
8111 );
8112 assert_eq!(
8113 dumb_http_pack_file_path("/repo.git/", &sha1).expect("test operation should succeed"),
8114 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.pack"
8115 );
8116 assert_eq!(
8117 dumb_http_pack_index_path("/repo.git/", &sha1).expect("test operation should succeed"),
8118 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.idx"
8119 );
8120 assert_eq!(
8121 smart_http_rpc_path("/repo.git", GitService::ReceivePack)
8122 .expect("test operation should succeed"),
8123 "/repo.git/git-receive-pack"
8124 );
8125 assert_eq!(
8126 smart_http_advertisement_content_type(GitService::UploadPack)
8127 .expect("test operation should succeed"),
8128 "application/x-git-upload-pack-advertisement"
8129 );
8130 assert_eq!(
8131 smart_http_rpc_request_content_type(GitService::UploadPack)
8132 .expect("test operation should succeed"),
8133 "application/x-git-upload-pack-request"
8134 );
8135 assert_eq!(
8136 smart_http_rpc_result_content_type(GitService::ReceivePack)
8137 .expect("test operation should succeed"),
8138 "application/x-git-receive-pack-result"
8139 );
8140 assert_eq!(
8141 parse_smart_http_advertisement_content_type(
8142 "Application/X-Git-Upload-Pack-Advertisement"
8143 )
8144 .expect("test operation should succeed"),
8145 GitService::UploadPack
8146 );
8147 assert_eq!(
8148 parse_smart_http_rpc_request_content_type("application/x-git-receive-pack-request")
8149 .expect("test operation should succeed"),
8150 GitService::ReceivePack
8151 );
8152 assert_eq!(
8153 parse_smart_http_rpc_result_content_type("application/x-git-upload-pack-result")
8154 .expect("test operation should succeed"),
8155 GitService::UploadPack
8156 );
8157 }
8158
8159 #[test]
8160 fn smart_http_helpers_reject_invalid_services_paths_and_content_types() {
8161 let oid = ObjectId::from_hex(
8162 ObjectFormat::Sha1,
8163 "1111111111111111111111111111111111111111",
8164 )
8165 .expect("test operation should succeed");
8166 assert!(smart_http_info_refs_path("repo.git", GitService::UploadPack).is_err());
8167 assert!(smart_http_rpc_path("/repo.git?x=1", GitService::UploadPack).is_err());
8168 assert!(dumb_http_info_refs_path("repo.git").is_err());
8169 assert!(dumb_http_alternates_path("/repo.git#fragment").is_err());
8170 assert!(dumb_http_packs_path("/repo.git?query").is_err());
8171 assert!(dumb_http_loose_object_path("repo.git", &oid).is_err());
8172 assert!(dumb_http_pack_file_path("/repo.git#fragment", &oid).is_err());
8173 assert!(dumb_http_pack_index_path("/repo.git?query", &oid).is_err());
8174 assert!(smart_http_info_refs_path("/repo.git", GitService::UploadArchive).is_err());
8175 assert!(smart_http_advertisement_content_type(GitService::UploadArchive).is_err());
8176 assert!(
8177 parse_smart_http_advertisement_content_type(
8178 "application/x-git-upload-archive-advertisement"
8179 )
8180 .is_err()
8181 );
8182 assert!(
8183 parse_smart_http_rpc_request_content_type("application/x-git-upload-pack-result")
8184 .is_err()
8185 );
8186 assert!(
8187 parse_smart_http_rpc_result_content_type(
8188 "application/x-git-receive-pack-result; charset=utf-8"
8189 )
8190 .is_err()
8191 );
8192 }
8193
8194 #[test]
8195 fn sideband_packets_parse_and_encode_channels() {
8196 let payloads = vec![
8197 b"\x01PACK bytes".to_vec(),
8198 b"\x02counting objects\n".to_vec(),
8199 b"\x03fatal error\n".to_vec(),
8200 ];
8201 let packets = parse_sideband_packets(&payloads).expect("test operation should succeed");
8202 assert_eq!(
8203 packets,
8204 vec![
8205 SideBandPacket {
8206 channel: SideBandChannel::Data,
8207 data: b"PACK bytes".to_vec(),
8208 },
8209 SideBandPacket {
8210 channel: SideBandChannel::Progress,
8211 data: b"counting objects\n".to_vec(),
8212 },
8213 SideBandPacket {
8214 channel: SideBandChannel::Fatal,
8215 data: b"fatal error\n".to_vec(),
8216 },
8217 ]
8218 );
8219 assert_eq!(
8220 encode_sideband_packets(&packets).expect("test operation should succeed"),
8221 payloads
8222 );
8223 }
8224
8225 #[test]
8226 fn sideband_stream_parses_encodes_and_demuxes_packets() {
8227 let frames = vec![
8228 PktLineFrame::Data(vec![1, b'P', b'A']),
8229 PktLineFrame::Data(vec![2, b'c', b'o', b'u', b'n', b't', b'\n']),
8230 PktLineFrame::Data(vec![1, b'C', b'K']),
8231 PktLineFrame::Flush,
8232 ];
8233 let packets = parse_sideband_stream(&frames).expect("test operation should succeed");
8234 assert_eq!(
8235 packets,
8236 vec![
8237 SideBandPacket {
8238 channel: SideBandChannel::Data,
8239 data: b"PA".to_vec(),
8240 },
8241 SideBandPacket {
8242 channel: SideBandChannel::Progress,
8243 data: b"count\n".to_vec(),
8244 },
8245 SideBandPacket {
8246 channel: SideBandChannel::Data,
8247 data: b"CK".to_vec(),
8248 },
8249 ]
8250 );
8251 assert_eq!(
8252 encode_sideband_stream(&packets).expect("test operation should succeed"),
8253 frames
8254 );
8255 assert_eq!(
8256 demux_sideband_stream(&frames).expect("test operation should succeed"),
8257 SideBandDemux {
8258 data: b"PACK".to_vec(),
8259 progress: vec![b"count\n".to_vec()],
8260 }
8261 );
8262 }
8263
8264 #[test]
8265 fn sideband_stream_reads_and_writes_until_flush() {
8266 let packets = vec![
8267 SideBandPacket {
8268 channel: SideBandChannel::Data,
8269 data: b"PACK".to_vec(),
8270 },
8271 SideBandPacket {
8272 channel: SideBandChannel::Progress,
8273 data: b"done\n".to_vec(),
8274 },
8275 ];
8276 let mut encoded = Vec::new();
8277 write_sideband_stream(&mut encoded, &packets).expect("test operation should succeed");
8278 encoded.extend_from_slice(b"tail");
8279
8280 let mut input = encoded.as_slice();
8281 assert_eq!(
8282 read_sideband_stream(&mut input).expect("test operation should succeed"),
8283 packets
8284 );
8285 assert_eq!(input, b"tail");
8286
8287 let mut input = encoded.as_slice();
8288 assert_eq!(
8289 read_and_demux_sideband_stream(&mut input).expect("test operation should succeed"),
8290 SideBandDemux {
8291 data: b"PACK".to_vec(),
8292 progress: vec![b"done\n".to_vec()],
8293 }
8294 );
8295 assert_eq!(input, b"tail");
8296 }
8297
8298 #[test]
8299 fn sideband_packets_demux_data_and_progress() {
8300 let payloads = vec![
8301 b"\x01PACK".to_vec(),
8302 b"\x02counting objects\n".to_vec(),
8303 b"\x01 bytes".to_vec(),
8304 b"\x02done\n".to_vec(),
8305 ];
8306 assert_eq!(
8307 parse_and_demux_sideband_packets(&payloads).expect("test operation should succeed"),
8308 SideBandDemux {
8309 data: b"PACK bytes".to_vec(),
8310 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
8311 }
8312 );
8313 }
8314
8315 #[test]
8316 fn sideband_packets_reject_bad_channels_and_oversize_payloads() {
8317 assert!(parse_sideband_packet(b"").is_err());
8318 assert!(parse_sideband_packet(b"\x04bad").is_err());
8319 assert!(
8320 parse_sideband_stream(&[PktLineFrame::Data(vec![1, b'P', b'A', b'C', b'K'])]).is_err()
8321 );
8322 assert!(parse_sideband_stream(&[PktLineFrame::Delimiter, PktLineFrame::Flush]).is_err());
8323 assert!(
8324 parse_sideband_stream(&[
8325 PktLineFrame::Data(vec![1, b'P', b'A']),
8326 PktLineFrame::Flush,
8327 PktLineFrame::Data(vec![1, b'C', b'K']),
8328 ])
8329 .is_err()
8330 );
8331 assert!(
8332 parse_sideband_stream(&[
8333 PktLineFrame::Data(vec![1, b'P', b'A']),
8334 PktLineFrame::Data(b"\x04bad".to_vec()),
8335 PktLineFrame::Flush,
8336 ])
8337 .is_err()
8338 );
8339 assert!(
8340 encode_sideband_packet(&SideBandPacket {
8341 channel: SideBandChannel::Data,
8342 data: vec![0; PKT_LINE_MAX_PAYLOAD_LEN],
8343 })
8344 .is_err()
8345 );
8346 assert!(
8347 demux_sideband_packets(&[SideBandPacket {
8348 channel: SideBandChannel::Fatal,
8349 data: b"remote died\n".to_vec(),
8350 }])
8351 .is_err()
8352 );
8353 }
8354
8355 #[test]
8356 fn upload_archive_request_parses_and_encodes_arguments() {
8357 let frames = vec![
8358 PktLineFrame::Data(b"argument --format=tar\n".to_vec()),
8359 PktLineFrame::Data(b"argument HEAD:dir with spaces\n".to_vec()),
8360 PktLineFrame::Flush,
8361 ];
8362 let request = parse_upload_archive_request(&frames).expect("test operation should succeed");
8363 assert_eq!(
8364 request,
8365 UploadArchiveRequest {
8366 arguments: vec!["--format=tar".into(), "HEAD:dir with spaces".into()],
8367 }
8368 );
8369 assert_eq!(
8370 encode_upload_archive_request(&request).expect("test operation should succeed"),
8371 frames
8372 );
8373 }
8374
8375 #[test]
8376 fn upload_archive_request_streams_round_trip() {
8377 let request = UploadArchiveRequest {
8378 arguments: vec!["--prefix=src/".into(), "main".into()],
8379 };
8380 let mut encoded = Vec::new();
8381 write_upload_archive_request(&mut encoded, &request)
8382 .expect("test operation should succeed");
8383 encoded.extend_from_slice(b"tail");
8384
8385 let mut input = encoded.as_slice();
8386 assert_eq!(
8387 read_upload_archive_request(&mut input).expect("test operation should succeed"),
8388 request
8389 );
8390 assert_eq!(input, b"tail");
8391 }
8392
8393 #[test]
8394 fn upload_archive_request_rejects_malformed_streams() {
8395 assert!(parse_upload_archive_request(&[PktLineFrame::Flush]).is_err());
8396 assert!(
8397 parse_upload_archive_request(&[
8398 PktLineFrame::Data(b"--format=tar\n".to_vec()),
8399 PktLineFrame::Flush,
8400 ])
8401 .is_err()
8402 );
8403 assert!(
8404 parse_upload_archive_request(&[
8405 PktLineFrame::Data(b"argument HEAD\n".to_vec()),
8406 PktLineFrame::Delimiter,
8407 PktLineFrame::Flush,
8408 ])
8409 .is_err()
8410 );
8411 assert!(
8412 encode_upload_archive_request(&UploadArchiveRequest {
8413 arguments: vec!["bad\narg".into()],
8414 })
8415 .is_err()
8416 );
8417 }
8418
8419 #[test]
8420 fn upload_archive_response_parses_ack_sideband_and_nack() {
8421 let ack_frames = vec![
8422 PktLineFrame::Data(b"ACK\n".to_vec()),
8423 PktLineFrame::Data(b"\x01tar bytes".to_vec()),
8424 PktLineFrame::Data(b"\x02progress\n".to_vec()),
8425 PktLineFrame::Flush,
8426 ];
8427 let response =
8428 parse_upload_archive_response(&ack_frames).expect("test operation should succeed");
8429 assert_eq!(
8430 response,
8431 UploadArchiveResponse::Ack {
8432 sideband: vec![
8433 SideBandPacket {
8434 channel: SideBandChannel::Data,
8435 data: b"tar bytes".to_vec(),
8436 },
8437 SideBandPacket {
8438 channel: SideBandChannel::Progress,
8439 data: b"progress\n".to_vec(),
8440 },
8441 ],
8442 }
8443 );
8444 assert_eq!(
8445 encode_upload_archive_response(&response).expect("test operation should succeed"),
8446 ack_frames
8447 );
8448 assert_eq!(
8449 demux_upload_archive_response(&response).expect("test operation should succeed"),
8450 SideBandDemux {
8451 data: b"tar bytes".to_vec(),
8452 progress: vec![b"progress\n".to_vec()],
8453 }
8454 );
8455
8456 let nack = UploadArchiveResponse::Nack {
8457 message: "unreachable tree".into(),
8458 };
8459 let nack_frames = vec![
8460 PktLineFrame::Data(b"NACK unreachable tree\n".to_vec()),
8461 PktLineFrame::Flush,
8462 ];
8463 assert_eq!(
8464 parse_upload_archive_response(&nack_frames).expect("test operation should succeed"),
8465 nack
8466 );
8467 assert_eq!(
8468 encode_upload_archive_response(&nack).expect("test operation should succeed"),
8469 nack_frames
8470 );
8471 assert!(demux_upload_archive_response(&nack).is_err());
8472 }
8473
8474 #[test]
8475 fn upload_archive_response_streams_round_trip() {
8476 let response = UploadArchiveResponse::Ack {
8477 sideband: vec![SideBandPacket {
8478 channel: SideBandChannel::Data,
8479 data: b"tar bytes".to_vec(),
8480 }],
8481 };
8482 let mut encoded = Vec::new();
8483 write_upload_archive_response(&mut encoded, &response)
8484 .expect("test operation should succeed");
8485 encoded.extend_from_slice(b"tail");
8486
8487 let mut input = encoded.as_slice();
8488 assert_eq!(
8489 read_upload_archive_response(&mut input).expect("test operation should succeed"),
8490 response
8491 );
8492 assert_eq!(input, b"tail");
8493 }
8494
8495 #[test]
8496 fn upload_archive_response_rejects_malformed_streams() {
8497 assert!(parse_upload_archive_response(&[]).is_err());
8498 assert!(
8499 parse_upload_archive_response(&[
8500 PktLineFrame::Data(b"ACK\n".to_vec()),
8501 PktLineFrame::Flush,
8502 PktLineFrame::Data(b"\x01tail".to_vec()),
8503 ])
8504 .is_err()
8505 );
8506 assert!(
8507 parse_upload_archive_response(&[
8508 PktLineFrame::Data(b"NACK\n".to_vec()),
8509 PktLineFrame::Flush,
8510 ])
8511 .is_err()
8512 );
8513 assert!(
8514 parse_upload_archive_response(&[
8515 PktLineFrame::Data(b"NACK denied\n".to_vec()),
8516 PktLineFrame::Data(b"\x02extra\n".to_vec()),
8517 PktLineFrame::Flush,
8518 ])
8519 .is_err()
8520 );
8521 assert!(
8522 encode_upload_archive_response(&UploadArchiveResponse::Nack {
8523 message: "bad\nmessage".into(),
8524 })
8525 .is_err()
8526 );
8527 }
8528
8529 #[test]
8530 fn capabilities_parse_and_encode_tokens() {
8531 let capabilities = parse_capabilities(
8532 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main\n",
8533 )
8534 .expect("test operation should succeed");
8535 assert_eq!(
8536 capabilities,
8537 vec![
8538 Capability {
8539 name: "multi_ack".into(),
8540 value: None,
8541 },
8542 Capability {
8543 name: "thin-pack".into(),
8544 value: None,
8545 },
8546 Capability {
8547 name: "agent".into(),
8548 value: Some("git/2.54.0".into()),
8549 },
8550 Capability {
8551 name: "symref".into(),
8552 value: Some("HEAD:refs/heads/main".into()),
8553 },
8554 ]
8555 );
8556 assert_eq!(
8557 encode_capabilities(&capabilities).expect("test operation should succeed"),
8558 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main"
8559 );
8560 }
8561
8562 #[test]
8563 fn capabilities_reject_empty_or_delimited_fields() {
8564 assert!(parse_capabilities(b"multi_ack thin-pack").is_err());
8565 assert!(parse_capabilities(b"agent=").is_err());
8566 assert!(parse_capabilities(b"symref=HEAD:refs/heads/main\nbad").is_err());
8567 assert!(
8568 encode_capabilities(&[Capability {
8569 name: "bad name".into(),
8570 value: None,
8571 }])
8572 .is_err()
8573 );
8574 }
8575
8576 #[test]
8577 fn protocol_v2_object_format_uses_capability_or_defaults_to_sha1() {
8578 assert_eq!(
8579 protocol_v2_object_format(&[]).expect("test operation should succeed"),
8580 ObjectFormat::Sha1
8581 );
8582 assert_eq!(
8583 protocol_v2_object_format(&[Capability {
8584 name: "object-format".into(),
8585 value: Some("sha256".into()),
8586 }])
8587 .expect("test operation should succeed"),
8588 ObjectFormat::Sha256
8589 );
8590 assert!(
8591 protocol_v2_object_format(&[Capability {
8592 name: "object-format".into(),
8593 value: None,
8594 }])
8595 .is_err()
8596 );
8597 assert!(
8598 protocol_v2_object_format(&[
8599 Capability {
8600 name: "object-format".into(),
8601 value: Some("sha1".into()),
8602 },
8603 Capability {
8604 name: "object-format".into(),
8605 value: Some("sha256".into()),
8606 },
8607 ])
8608 .is_err()
8609 );
8610 assert!(
8611 protocol_v2_object_format(&[Capability {
8612 name: "object-format".into(),
8613 value: Some("unknown".into()),
8614 }])
8615 .is_err()
8616 );
8617 }
8618
8619 #[test]
8620 fn protocol_v2_command_request_capabilities_validate_against_handshake() {
8621 let handshake = TransportHandshake {
8622 protocol: ProtocolVersion::V2,
8623 capabilities: vec![
8624 Capability {
8625 name: "fetch".into(),
8626 value: Some("shallow filter".into()),
8627 },
8628 Capability {
8629 name: "agent".into(),
8630 value: Some("sley/0".into()),
8631 },
8632 Capability {
8633 name: "object-format".into(),
8634 value: Some("sha1".into()),
8635 },
8636 ],
8637 };
8638 validate_protocol_v2_command_request_capabilities(
8639 &handshake,
8640 &ProtocolV2CommandRequest {
8641 command: "fetch".into(),
8642 capabilities: vec![
8643 Capability {
8644 name: "agent".into(),
8645 value: Some("client/1".into()),
8646 },
8647 Capability {
8648 name: "object-format".into(),
8649 value: Some("sha1".into()),
8650 },
8651 ],
8652 arguments: Vec::new(),
8653 },
8654 )
8655 .expect("test operation should succeed");
8656 assert!(
8657 validate_protocol_v2_command_request_capabilities(
8658 &handshake,
8659 &ProtocolV2CommandRequest {
8660 command: "ls-refs".into(),
8661 capabilities: Vec::new(),
8662 arguments: Vec::new(),
8663 },
8664 )
8665 .is_err()
8666 );
8667 assert!(
8668 validate_protocol_v2_command_request_capabilities(
8669 &handshake,
8670 &ProtocolV2CommandRequest {
8671 command: "fetch".into(),
8672 capabilities: vec![Capability {
8673 name: "server-option".into(),
8674 value: None,
8675 }],
8676 arguments: Vec::new(),
8677 },
8678 )
8679 .is_err()
8680 );
8681 assert!(
8682 validate_protocol_v2_command_request_capabilities(
8683 &handshake,
8684 &ProtocolV2CommandRequest {
8685 command: "fetch".into(),
8686 capabilities: vec![Capability {
8687 name: "object-format".into(),
8688 value: Some("sha256".into()),
8689 }],
8690 arguments: Vec::new(),
8691 },
8692 )
8693 .is_err()
8694 );
8695 assert!(
8696 validate_protocol_v2_command_request_capabilities(
8697 &handshake,
8698 &ProtocolV2CommandRequest {
8699 command: "fetch".into(),
8700 capabilities: vec![Capability {
8701 name: "agent".into(),
8702 value: None,
8703 }],
8704 arguments: Vec::new(),
8705 },
8706 )
8707 .is_err()
8708 );
8709 }
8710
8711 #[test]
8712 fn protocol_v2_command_options_parse_and_encode_known_capabilities() {
8713 let capabilities = vec![
8714 Capability {
8715 name: "agent".into(),
8716 value: Some("sley/0".into()),
8717 },
8718 Capability {
8719 name: "object-format".into(),
8720 value: Some("sha256".into()),
8721 },
8722 Capability {
8723 name: "server-option".into(),
8724 value: Some("trace=true".into()),
8725 },
8726 Capability {
8727 name: "server-option".into(),
8728 value: Some("region=west".into()),
8729 },
8730 Capability {
8731 name: "session-id".into(),
8732 value: Some("abc123".into()),
8733 },
8734 ];
8735 let options = parse_protocol_v2_command_options(&capabilities)
8736 .expect("test operation should succeed");
8737 assert_eq!(
8738 options,
8739 ProtocolV2CommandOptions {
8740 agent: Some("sley/0".into()),
8741 object_format: Some(ObjectFormat::Sha256),
8742 server_options: vec!["trace=true".into(), "region=west".into()],
8743 extra: vec![Capability {
8744 name: "session-id".into(),
8745 value: Some("abc123".into()),
8746 }],
8747 }
8748 );
8749 assert_eq!(
8750 encode_protocol_v2_command_options(&options).expect("test operation should succeed"),
8751 capabilities
8752 );
8753 }
8754
8755 #[test]
8756 fn protocol_v2_command_options_reject_malformed_known_capabilities() {
8757 assert!(
8758 parse_protocol_v2_command_options(&[
8759 Capability {
8760 name: "agent".into(),
8761 value: Some("sley/0".into()),
8762 },
8763 Capability {
8764 name: "agent".into(),
8765 value: Some("sley/1".into()),
8766 },
8767 ])
8768 .is_err()
8769 );
8770 assert!(
8771 parse_protocol_v2_command_options(&[Capability {
8772 name: "object-format".into(),
8773 value: Some("sha512".into()),
8774 }])
8775 .is_err()
8776 );
8777 assert!(
8778 parse_protocol_v2_command_options(&[Capability {
8779 name: "server-option".into(),
8780 value: None,
8781 }])
8782 .is_err()
8783 );
8784 assert!(
8785 encode_protocol_v2_command_options(&ProtocolV2CommandOptions {
8786 extra: vec![Capability {
8787 name: "server-option".into(),
8788 value: Some("trace=true".into()),
8789 }],
8790 ..ProtocolV2CommandOptions::default()
8791 })
8792 .is_err()
8793 );
8794 }
8795
8796 #[test]
8797 fn protocol_v2_ls_refs_features_parse_and_encode_advertisement() {
8798 let capabilities = vec![Capability {
8799 name: "ls-refs".into(),
8800 value: Some("unborn custom".into()),
8801 }];
8802 let features = parse_protocol_v2_ls_refs_features(&capabilities)
8803 .expect("test operation should succeed")
8804 .expect("test operation should succeed");
8805 assert_eq!(
8806 features,
8807 ProtocolV2LsRefsFeatures {
8808 unborn: true,
8809 unknown: vec!["custom".into()],
8810 }
8811 );
8812 assert_eq!(
8813 encode_protocol_v2_ls_refs_capability(&features)
8814 .expect("test operation should succeed"),
8815 capabilities[0]
8816 );
8817 assert_eq!(
8818 parse_protocol_v2_ls_refs_features(&[Capability {
8819 name: "ls-refs".into(),
8820 value: None,
8821 }])
8822 .expect("test operation should succeed")
8823 .expect("test operation should succeed"),
8824 ProtocolV2LsRefsFeatures::default()
8825 );
8826 assert!(
8827 parse_protocol_v2_ls_refs_features(&[Capability {
8828 name: "fetch".into(),
8829 value: Some("filter".into()),
8830 }])
8831 .expect("test operation should succeed")
8832 .is_none()
8833 );
8834 }
8835
8836 #[test]
8837 fn protocol_v2_ls_refs_features_reject_malformed_advertisements() {
8838 assert!(
8839 parse_protocol_v2_ls_refs_features(&[
8840 Capability {
8841 name: "ls-refs".into(),
8842 value: None,
8843 },
8844 Capability {
8845 name: "ls-refs".into(),
8846 value: None,
8847 },
8848 ])
8849 .is_err()
8850 );
8851 assert!(
8852 parse_protocol_v2_ls_refs_features(&[Capability {
8853 name: "ls-refs".into(),
8854 value: Some("unborn custom".into()),
8855 }])
8856 .is_err()
8857 );
8858 assert!(
8859 encode_protocol_v2_ls_refs_capability(&ProtocolV2LsRefsFeatures {
8860 unknown: vec!["unborn".into()],
8861 ..ProtocolV2LsRefsFeatures::default()
8862 })
8863 .is_err()
8864 );
8865 }
8866
8867 #[test]
8868 fn protocol_v2_ls_refs_command_request_validates_unborn_feature() {
8869 let handshake = TransportHandshake {
8870 protocol: ProtocolVersion::V2,
8871 capabilities: vec![Capability {
8872 name: "ls-refs".into(),
8873 value: Some("unborn".into()),
8874 }],
8875 };
8876 let request = ProtocolV2CommandRequest {
8877 command: "ls-refs".into(),
8878 capabilities: Vec::new(),
8879 arguments: vec![b"unborn".to_vec(), b"ref-prefix HEAD".to_vec()],
8880 };
8881 let parsed = validate_protocol_v2_ls_refs_command_request(&handshake, &request)
8882 .expect("test operation should succeed");
8883 assert!(parsed.unborn);
8884 assert_eq!(parsed.ref_prefixes, vec!["HEAD"]);
8885
8886 let blocked = TransportHandshake {
8887 protocol: ProtocolVersion::V2,
8888 capabilities: vec![Capability {
8889 name: "ls-refs".into(),
8890 value: None,
8891 }],
8892 };
8893 assert!(validate_protocol_v2_ls_refs_command_request(&blocked, &request).is_err());
8894 }
8895
8896 #[test]
8897 fn protocol_v2_fetch_features_parse_and_encode_advertisement() {
8898 let capabilities = vec![Capability {
8899 name: "fetch".into(),
8900 value: Some(
8901 "shallow wait-for-done filter ref-in-want sideband-all packfile-uris custom".into(),
8902 ),
8903 }];
8904 let features = parse_protocol_v2_fetch_features(&capabilities)
8905 .expect("test operation should succeed")
8906 .expect("test operation should succeed");
8907 assert_eq!(
8908 features,
8909 ProtocolV2FetchFeatures {
8910 shallow: true,
8911 wait_for_done: true,
8912 filter: true,
8913 ref_in_want: true,
8914 sideband_all: true,
8915 packfile_uris: true,
8916 unknown: vec!["custom".into()],
8917 }
8918 );
8919 assert_eq!(
8920 encode_protocol_v2_fetch_capability(&features).expect("test operation should succeed"),
8921 capabilities[0]
8922 );
8923 assert_eq!(
8924 parse_protocol_v2_fetch_features(&[Capability {
8925 name: "fetch".into(),
8926 value: None,
8927 }])
8928 .expect("test operation should succeed")
8929 .expect("test operation should succeed"),
8930 ProtocolV2FetchFeatures::default()
8931 );
8932 assert!(
8933 parse_protocol_v2_fetch_features(&[])
8934 .expect("test operation should succeed")
8935 .is_none()
8936 );
8937 }
8938
8939 #[test]
8940 fn protocol_v2_fetch_features_reject_malformed_advertisements() {
8941 assert!(
8942 parse_protocol_v2_fetch_features(&[
8943 Capability {
8944 name: "fetch".into(),
8945 value: None,
8946 },
8947 Capability {
8948 name: "fetch".into(),
8949 value: None,
8950 },
8951 ])
8952 .is_err()
8953 );
8954 assert!(
8955 parse_protocol_v2_fetch_features(&[Capability {
8956 name: "fetch".into(),
8957 value: Some("filter shallow".into()),
8958 }])
8959 .is_err()
8960 );
8961 assert!(
8962 encode_protocol_v2_fetch_capability(&ProtocolV2FetchFeatures {
8963 unknown: vec!["filter".into()],
8964 ..ProtocolV2FetchFeatures::default()
8965 })
8966 .is_err()
8967 );
8968 }
8969
8970 #[test]
8971 fn protocol_v2_fetch_request_features_validate_feature_gated_arguments() {
8972 let features = ProtocolV2FetchFeatures {
8973 shallow: true,
8974 wait_for_done: true,
8975 filter: true,
8976 ref_in_want: true,
8977 sideband_all: true,
8978 packfile_uris: true,
8979 unknown: Vec::new(),
8980 };
8981 validate_protocol_v2_fetch_request_features(
8982 &features,
8983 &ProtocolV2FetchRequest {
8984 want_refs: vec!["refs/heads/main".into()],
8985 shallow: vec![
8986 ObjectId::from_hex(
8987 ObjectFormat::Sha1,
8988 "1111111111111111111111111111111111111111",
8989 )
8990 .expect("test operation should succeed"),
8991 ],
8992 deepen: Some(1),
8993 filter: Some("blob:none".into()),
8994 packfile_uris: Some("https".into()),
8995 sideband_all: true,
8996 wait_for_done: true,
8997 ..ProtocolV2FetchRequest::default()
8998 },
8999 )
9000 .expect("test operation should succeed");
9001
9002 let request = ProtocolV2FetchRequest {
9003 want_refs: vec!["refs/heads/main".into()],
9004 filter: Some("blob:none".into()),
9005 sideband_all: true,
9006 ..ProtocolV2FetchRequest::default()
9007 };
9008 assert!(
9009 validate_protocol_v2_fetch_request_features(
9010 &ProtocolV2FetchFeatures::default(),
9011 &request,
9012 )
9013 .is_err()
9014 );
9015 assert!(
9016 validate_protocol_v2_fetch_request_features(
9017 &ProtocolV2FetchFeatures {
9018 ref_in_want: true,
9019 filter: true,
9020 ..ProtocolV2FetchFeatures::default()
9021 },
9022 &request,
9023 )
9024 .is_err()
9025 );
9026 }
9027
9028 #[test]
9029 fn protocol_v2_fetch_command_request_validates_against_handshake_features() {
9030 let handshake = TransportHandshake {
9031 protocol: ProtocolVersion::V2,
9032 capabilities: vec![
9033 Capability {
9034 name: "fetch".into(),
9035 value: Some("filter ref-in-want".into()),
9036 },
9037 Capability {
9038 name: "agent".into(),
9039 value: Some("sley/0".into()),
9040 },
9041 ],
9042 };
9043 let request = ProtocolV2CommandRequest {
9044 command: "fetch".into(),
9045 capabilities: vec![Capability {
9046 name: "agent".into(),
9047 value: Some("client/1".into()),
9048 }],
9049 arguments: vec![
9050 b"want-ref refs/heads/main".to_vec(),
9051 b"filter blob:none".to_vec(),
9052 ],
9053 };
9054 let fetch =
9055 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &request)
9056 .expect("test operation should succeed");
9057 assert_eq!(fetch.want_refs, vec!["refs/heads/main"]);
9058 assert_eq!(fetch.filter.as_deref(), Some("blob:none"));
9059
9060 let mut bad = request.clone();
9061 bad.arguments.push(b"sideband-all".to_vec());
9062 assert!(
9063 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &bad)
9064 .is_err()
9065 );
9066 }
9067
9068 #[test]
9069 fn protocol_v2_object_info_request_parses_encodes_and_validates() {
9070 let oid = ObjectId::from_hex(
9071 ObjectFormat::Sha1,
9072 "1111111111111111111111111111111111111111",
9073 )
9074 .expect("test operation should succeed");
9075 let request = ProtocolV2CommandRequest {
9076 command: "object-info".into(),
9077 capabilities: Vec::new(),
9078 arguments: vec![
9079 b"size".to_vec(),
9080 b"oid 1111111111111111111111111111111111111111".to_vec(),
9081 ],
9082 };
9083 let parsed =
9084 ProtocolV2ObjectInfoRequest::from_command_request(ObjectFormat::Sha1, &request)
9085 .expect("test operation should succeed");
9086 assert_eq!(
9087 parsed,
9088 ProtocolV2ObjectInfoRequest {
9089 size: true,
9090 oids: vec![oid],
9091 }
9092 );
9093 assert_eq!(
9094 parsed
9095 .to_command_request()
9096 .expect("test operation should succeed"),
9097 request
9098 );
9099
9100 let handshake = TransportHandshake {
9101 protocol: ProtocolVersion::V2,
9102 capabilities: vec![Capability {
9103 name: "object-info".into(),
9104 value: None,
9105 }],
9106 };
9107 assert_eq!(
9108 validate_protocol_v2_object_info_command_request(
9109 &handshake,
9110 ObjectFormat::Sha1,
9111 &request,
9112 )
9113 .expect("test operation should succeed"),
9114 parsed
9115 );
9116 }
9117
9118 #[test]
9119 fn protocol_v2_object_info_request_streams_round_trip() {
9120 let request = ProtocolV2ObjectInfoRequest {
9121 size: true,
9122 oids: vec![
9123 ObjectId::from_hex(
9124 ObjectFormat::Sha1,
9125 "1111111111111111111111111111111111111111",
9126 )
9127 .expect("test operation should succeed"),
9128 ],
9129 };
9130 let mut encoded = Vec::new();
9131 write_protocol_v2_object_info_request(&mut encoded, &request)
9132 .expect("test operation should succeed");
9133 encoded.extend_from_slice(b"tail");
9134
9135 let mut input = encoded.as_slice();
9136 assert_eq!(
9137 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut input)
9138 .expect("test operation should succeed"),
9139 request
9140 );
9141 assert_eq!(input, b"tail");
9142 }
9143
9144 #[test]
9145 fn protocol_v2_object_info_request_rejects_malformed_arguments() {
9146 assert!(
9147 ProtocolV2ObjectInfoRequest::from_command_request(
9148 ObjectFormat::Sha1,
9149 &ProtocolV2CommandRequest {
9150 command: "object-info".into(),
9151 capabilities: Vec::new(),
9152 arguments: vec![b"oid 1111111111111111111111111111111111111111".to_vec()],
9153 },
9154 )
9155 .is_err()
9156 );
9157 assert!(
9158 ProtocolV2ObjectInfoRequest::from_command_request(
9159 ObjectFormat::Sha1,
9160 &ProtocolV2CommandRequest {
9161 command: "object-info".into(),
9162 capabilities: Vec::new(),
9163 arguments: vec![b"size".to_vec(), b"size".to_vec()],
9164 },
9165 )
9166 .is_err()
9167 );
9168 assert!(
9169 ProtocolV2ObjectInfoRequest::from_command_request(
9170 ObjectFormat::Sha1,
9171 &ProtocolV2CommandRequest {
9172 command: "object-info".into(),
9173 capabilities: Vec::new(),
9174 arguments: vec![b"size".to_vec()],
9175 },
9176 )
9177 .is_err()
9178 );
9179 assert!(
9180 ProtocolV2ObjectInfoRequest::from_command_request(
9181 ObjectFormat::Sha1,
9182 &ProtocolV2CommandRequest {
9183 command: "object-info".into(),
9184 capabilities: Vec::new(),
9185 arguments: vec![b"size".to_vec(), b"oid not-an-oid".to_vec()],
9186 },
9187 )
9188 .is_err()
9189 );
9190 assert!(
9191 validate_protocol_v2_object_info_command_request(
9192 &TransportHandshake {
9193 protocol: ProtocolVersion::V2,
9194 capabilities: Vec::new(),
9195 },
9196 ObjectFormat::Sha1,
9197 &ProtocolV2CommandRequest {
9198 command: "object-info".into(),
9199 capabilities: Vec::new(),
9200 arguments: vec![
9201 b"size".to_vec(),
9202 b"oid 1111111111111111111111111111111111111111".to_vec(),
9203 ],
9204 },
9205 )
9206 .is_err()
9207 );
9208 }
9209
9210 #[test]
9211 fn protocol_v2_command_request_classifies_known_and_unknown_commands() {
9212 let handshake = TransportHandshake {
9213 protocol: ProtocolVersion::V2,
9214 capabilities: vec![
9215 Capability {
9216 name: "ls-refs".into(),
9217 value: Some("unborn".into()),
9218 },
9219 Capability {
9220 name: "fetch".into(),
9221 value: Some("filter ref-in-want".into()),
9222 },
9223 Capability {
9224 name: "object-info".into(),
9225 value: None,
9226 },
9227 Capability {
9228 name: "server-option".into(),
9229 value: None,
9230 },
9231 Capability {
9232 name: "server-info".into(),
9233 value: Some("custom".into()),
9234 },
9235 ],
9236 };
9237 assert_eq!(
9238 classify_protocol_v2_command_request(
9239 &handshake,
9240 ObjectFormat::Sha1,
9241 &ProtocolV2CommandRequest {
9242 command: "ls-refs".into(),
9243 capabilities: Vec::new(),
9244 arguments: vec![b"unborn".to_vec()],
9245 },
9246 )
9247 .expect("test operation should succeed"),
9248 ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9249 unborn: true,
9250 ..ProtocolV2LsRefsRequest::default()
9251 })
9252 );
9253 assert_eq!(
9254 classify_protocol_v2_command_request(
9255 &handshake,
9256 ObjectFormat::Sha1,
9257 &ProtocolV2CommandRequest {
9258 command: "fetch".into(),
9259 capabilities: Vec::new(),
9260 arguments: vec![
9261 b"want-ref refs/heads/main".to_vec(),
9262 b"filter blob:none".to_vec(),
9263 ],
9264 },
9265 )
9266 .expect("test operation should succeed"),
9267 ProtocolV2Command::Fetch(ProtocolV2FetchRequest {
9268 want_refs: vec!["refs/heads/main".into()],
9269 filter: Some("blob:none".into()),
9270 ..ProtocolV2FetchRequest::default()
9271 })
9272 );
9273 assert_eq!(
9274 classify_protocol_v2_command_request(
9275 &handshake,
9276 ObjectFormat::Sha1,
9277 &ProtocolV2CommandRequest {
9278 command: "object-info".into(),
9279 capabilities: Vec::new(),
9280 arguments: vec![
9281 b"size".to_vec(),
9282 b"oid 1111111111111111111111111111111111111111".to_vec(),
9283 ],
9284 },
9285 )
9286 .expect("test operation should succeed"),
9287 ProtocolV2Command::ObjectInfo(ProtocolV2ObjectInfoRequest {
9288 size: true,
9289 oids: vec![
9290 ObjectId::from_hex(
9291 ObjectFormat::Sha1,
9292 "1111111111111111111111111111111111111111",
9293 )
9294 .expect("test operation should succeed")
9295 ],
9296 })
9297 );
9298
9299 let unknown = ProtocolV2CommandRequest {
9300 command: "server-info".into(),
9301 capabilities: vec![Capability {
9302 name: "server-option".into(),
9303 value: Some("trace=true".into()),
9304 }],
9305 arguments: Vec::new(),
9306 };
9307 assert_eq!(
9308 classify_protocol_v2_command_request(&handshake, ObjectFormat::Sha1, &unknown)
9309 .expect("test operation should succeed"),
9310 ProtocolV2Command::Unknown(unknown)
9311 );
9312 assert!(
9313 classify_protocol_v2_command_request(
9314 &handshake,
9315 ObjectFormat::Sha1,
9316 &ProtocolV2CommandRequest {
9317 command: "not-advertised".into(),
9318 capabilities: Vec::new(),
9319 arguments: Vec::new(),
9320 },
9321 )
9322 .is_err()
9323 );
9324 }
9325
9326 #[test]
9327 fn protocol_v2_session_request_classifies_streamed_command_and_done() {
9328 let handshake = TransportHandshake {
9329 protocol: ProtocolVersion::V2,
9330 capabilities: vec![
9331 Capability {
9332 name: "ls-refs".into(),
9333 value: Some("unborn".into()),
9334 },
9335 Capability {
9336 name: "fetch".into(),
9337 value: Some("filter ref-in-want".into()),
9338 },
9339 ],
9340 };
9341 let command = ProtocolV2Request::Command(ProtocolV2CommandRequest {
9342 command: "ls-refs".into(),
9343 capabilities: Vec::new(),
9344 arguments: vec![b"unborn".to_vec()],
9345 });
9346 assert_eq!(
9347 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &command)
9348 .expect("test operation should succeed"),
9349 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9350 unborn: true,
9351 ..ProtocolV2LsRefsRequest::default()
9352 }))
9353 );
9354 assert_eq!(
9355 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &ProtocolV2Request::Done)
9356 .expect("test operation should succeed"),
9357 ProtocolV2SessionRequest::Done
9358 );
9359
9360 let mut encoded = Vec::new();
9361 write_protocol_v2_request(&mut encoded, &command).expect("test operation should succeed");
9362 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
9363 .expect("test operation should succeed");
9364 encoded.extend_from_slice(b"tail");
9365
9366 let mut input = encoded.as_slice();
9367 assert_eq!(
9368 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9369 .expect("test operation should succeed"),
9370 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9371 unborn: true,
9372 ..ProtocolV2LsRefsRequest::default()
9373 }))
9374 );
9375 assert_eq!(
9376 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9377 .expect("test operation should succeed"),
9378 ProtocolV2SessionRequest::Done
9379 );
9380 assert_eq!(input, b"tail");
9381 }
9382
9383 #[test]
9384 fn advertised_ref_parses_first_v0_capability_line() {
9385 let payload =
9386 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 HEAD\0multi_ack symref=HEAD:refs/heads/main\n";
9387 let advertisement = parse_ref_advertisement(ObjectFormat::Sha1, payload)
9388 .expect("test operation should succeed");
9389 assert_eq!(
9390 advertisement.oid,
9391 ObjectId::from_hex(
9392 ObjectFormat::Sha1,
9393 "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
9394 )
9395 .expect("test operation should succeed")
9396 );
9397 assert_eq!(advertisement.name, "HEAD");
9398 assert_eq!(
9399 advertisement.capabilities,
9400 vec![
9401 Capability {
9402 name: "multi_ack".into(),
9403 value: None,
9404 },
9405 Capability {
9406 name: "symref".into(),
9407 value: Some("HEAD:refs/heads/main".into()),
9408 },
9409 ]
9410 );
9411 }
9412
9413 #[test]
9414 fn advertised_ref_parses_lines_without_capabilities() {
9415 let advertisement = parse_ref_advertisement(
9416 ObjectFormat::Sha1,
9417 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 refs/heads/main\n",
9418 )
9419 .expect("test operation should succeed");
9420 assert_eq!(advertisement.name, "refs/heads/main");
9421 assert!(advertisement.capabilities.is_empty());
9422 }
9423
9424 #[test]
9425 fn advertised_ref_rejects_malformed_payloads() {
9426 assert!(
9427 parse_ref_advertisement(ObjectFormat::Sha1, b"not-an-oid refs/heads/main\n").is_err()
9428 );
9429 assert!(
9430 parse_ref_advertisement(
9431 ObjectFormat::Sha1,
9432 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n"
9433 )
9434 .is_err()
9435 );
9436 }
9437
9438 #[test]
9439 fn advertised_refs_parse_and_encode_stream() {
9440 let main = ObjectId::from_hex(
9441 ObjectFormat::Sha1,
9442 "1111111111111111111111111111111111111111",
9443 )
9444 .expect("test operation should succeed");
9445 let feature = ObjectId::from_hex(
9446 ObjectFormat::Sha1,
9447 "2222222222222222222222222222222222222222",
9448 )
9449 .expect("test operation should succeed");
9450 let frames = vec![
9451 PktLineFrame::Data(
9452 b"1111111111111111111111111111111111111111 HEAD\0multi_ack thin-pack agent=git/2.54.0\n"
9453 .to_vec(),
9454 ),
9455 PktLineFrame::Data(
9456 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9457 ),
9458 PktLineFrame::Flush,
9459 ];
9460 let advertisements = parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9461 .expect("test operation should succeed");
9462 assert_eq!(
9463 advertisements,
9464 vec![
9465 RefAdvertisement {
9466 oid: main,
9467 name: "HEAD".into(),
9468 capabilities: vec![
9469 Capability {
9470 name: "multi_ack".into(),
9471 value: None,
9472 },
9473 Capability {
9474 name: "thin-pack".into(),
9475 value: None,
9476 },
9477 Capability {
9478 name: "agent".into(),
9479 value: Some("git/2.54.0".into()),
9480 },
9481 ],
9482 },
9483 RefAdvertisement {
9484 oid: feature,
9485 name: "refs/heads/feature".into(),
9486 capabilities: Vec::new(),
9487 },
9488 ]
9489 );
9490 assert_eq!(
9491 encode_ref_advertisements(&advertisements).expect("test operation should succeed"),
9492 frames
9493 );
9494 assert_eq!(
9495 parse_ref_advertisements(ObjectFormat::Sha1, &[PktLineFrame::Flush])
9496 .expect("test operation should succeed"),
9497 Vec::<RefAdvertisement>::new()
9498 );
9499 }
9500
9501 #[test]
9502 fn advertised_ref_set_parses_v1_version_refs_and_shallow() {
9503 let main = ObjectId::from_hex(
9504 ObjectFormat::Sha1,
9505 "1111111111111111111111111111111111111111",
9506 )
9507 .expect("test operation should succeed");
9508 let feature = ObjectId::from_hex(
9509 ObjectFormat::Sha1,
9510 "2222222222222222222222222222222222222222",
9511 )
9512 .expect("test operation should succeed");
9513 let shallow = ObjectId::from_hex(
9514 ObjectFormat::Sha1,
9515 "3333333333333333333333333333333333333333",
9516 )
9517 .expect("test operation should succeed");
9518 let frames = vec![
9519 PktLineFrame::Data(b"version 1\n".to_vec()),
9520 PktLineFrame::Data(
9521 b"1111111111111111111111111111111111111111 HEAD\0multi_ack symref=HEAD:refs/heads/main\n"
9522 .to_vec(),
9523 ),
9524 PktLineFrame::Data(
9525 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9526 ),
9527 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
9528 PktLineFrame::Flush,
9529 ];
9530
9531 let set = parse_ref_advertisement_set(ObjectFormat::Sha1, &frames)
9532 .expect("test operation should succeed");
9533 assert_eq!(set.protocol, ProtocolVersion::V1);
9534 assert_eq!(set.shallow, vec![shallow]);
9535 assert_eq!(
9536 set.refs,
9537 vec![
9538 RefAdvertisement {
9539 oid: main,
9540 name: "HEAD".into(),
9541 capabilities: vec![
9542 Capability {
9543 name: "multi_ack".into(),
9544 value: None,
9545 },
9546 Capability {
9547 name: "symref".into(),
9548 value: Some("HEAD:refs/heads/main".into()),
9549 },
9550 ],
9551 },
9552 RefAdvertisement {
9553 oid: feature,
9554 name: "refs/heads/feature".into(),
9555 capabilities: Vec::new(),
9556 },
9557 ]
9558 );
9559 assert_eq!(
9560 parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9561 .expect("test operation should succeed"),
9562 set.refs
9563 );
9564 assert_eq!(
9565 encode_ref_advertisement_set(&set).expect("test operation should succeed"),
9566 frames
9567 );
9568 }
9569
9570 #[test]
9571 fn advertised_refs_streams_round_trip() {
9572 let advertisements = vec![RefAdvertisement {
9573 oid: ObjectId::from_hex(
9574 ObjectFormat::Sha1,
9575 "1111111111111111111111111111111111111111",
9576 )
9577 .expect("test operation should succeed"),
9578 name: "HEAD".into(),
9579 capabilities: vec![Capability {
9580 name: "symref".into(),
9581 value: Some("HEAD:refs/heads/main".into()),
9582 }],
9583 }];
9584 let mut encoded = Vec::new();
9585 write_ref_advertisements(&mut encoded, &advertisements)
9586 .expect("test operation should succeed");
9587 encoded.extend_from_slice(b"tail");
9588
9589 let mut input = encoded.as_slice();
9590 assert_eq!(
9591 read_ref_advertisements(ObjectFormat::Sha1, &mut input)
9592 .expect("test operation should succeed"),
9593 advertisements
9594 );
9595 assert_eq!(input, b"tail");
9596 }
9597
9598 #[test]
9599 fn advertised_ref_set_streams_round_trip() {
9600 let set = RefAdvertisementSet {
9601 protocol: ProtocolVersion::V1,
9602 refs: vec![RefAdvertisement {
9603 oid: ObjectId::from_hex(
9604 ObjectFormat::Sha1,
9605 "1111111111111111111111111111111111111111",
9606 )
9607 .expect("test operation should succeed"),
9608 name: "HEAD".into(),
9609 capabilities: vec![Capability {
9610 name: "symref".into(),
9611 value: Some("HEAD:refs/heads/main".into()),
9612 }],
9613 }],
9614 shallow: vec![
9615 ObjectId::from_hex(
9616 ObjectFormat::Sha1,
9617 "2222222222222222222222222222222222222222",
9618 )
9619 .expect("test operation should succeed"),
9620 ],
9621 };
9622 let mut encoded = Vec::new();
9623 write_ref_advertisement_set(&mut encoded, &set).expect("test operation should succeed");
9624 encoded.extend_from_slice(b"tail");
9625
9626 let mut input = encoded.as_slice();
9627 assert_eq!(
9628 read_ref_advertisement_set(ObjectFormat::Sha1, &mut input)
9629 .expect("test operation should succeed"),
9630 set
9631 );
9632 assert_eq!(input, b"tail");
9633 }
9634
9635 #[test]
9636 fn advertised_refs_reject_malformed_streams() {
9637 assert!(
9638 parse_ref_advertisements(
9639 ObjectFormat::Sha1,
9640 &[PktLineFrame::Data(
9641 b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),
9642 )],
9643 )
9644 .is_err()
9645 );
9646 assert!(
9647 parse_ref_advertisements(
9648 ObjectFormat::Sha1,
9649 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
9650 )
9651 .is_err()
9652 );
9653 assert!(parse_ref_advertisements(
9654 ObjectFormat::Sha1,
9655 &[
9656 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9657 PktLineFrame::Data(
9658 b"2222222222222222222222222222222222222222 refs/heads/main\0thin-pack\n"
9659 .to_vec(),
9660 ),
9661 PktLineFrame::Flush,
9662 ],
9663 )
9664 .is_err());
9665 assert!(parse_ref_advertisement_set(
9666 ObjectFormat::Sha1,
9667 &[
9668 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9669 PktLineFrame::Data(b"version 1\n".to_vec()),
9670 PktLineFrame::Flush,
9671 ],
9672 )
9673 .is_err());
9674 assert!(
9675 parse_ref_advertisement_set(
9676 ObjectFormat::Sha1,
9677 &[
9678 PktLineFrame::Data(b"version 2\n".to_vec()),
9679 PktLineFrame::Flush,
9680 ],
9681 )
9682 .is_err()
9683 );
9684 assert!(
9685 parse_ref_advertisement_set(
9686 ObjectFormat::Sha1,
9687 &[
9688 PktLineFrame::Data(
9689 b"shallow 1111111111111111111111111111111111111111\n".to_vec()
9690 ),
9691 PktLineFrame::Flush,
9692 ],
9693 )
9694 .is_err()
9695 );
9696 assert!(parse_ref_advertisement_set(
9697 ObjectFormat::Sha1,
9698 &[
9699 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9700 PktLineFrame::Data(b"shallow not-an-oid\n".to_vec()),
9701 PktLineFrame::Flush,
9702 ],
9703 )
9704 .is_err());
9705 assert!(parse_ref_advertisement_set(
9706 ObjectFormat::Sha1,
9707 &[
9708 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9709 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
9710 PktLineFrame::Data(
9711 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
9712 ),
9713 PktLineFrame::Flush,
9714 ],
9715 )
9716 .is_err());
9717 assert!(
9718 encode_ref_advertisements(&[
9719 RefAdvertisement {
9720 oid: ObjectId::from_hex(
9721 ObjectFormat::Sha1,
9722 "1111111111111111111111111111111111111111",
9723 )
9724 .expect("test operation should succeed"),
9725 name: "HEAD".into(),
9726 capabilities: Vec::new(),
9727 },
9728 RefAdvertisement {
9729 oid: ObjectId::from_hex(
9730 ObjectFormat::Sha1,
9731 "2222222222222222222222222222222222222222",
9732 )
9733 .expect("test operation should succeed"),
9734 name: "refs/heads/main".into(),
9735 capabilities: vec![Capability {
9736 name: "thin-pack".into(),
9737 value: None,
9738 }],
9739 },
9740 ])
9741 .is_err()
9742 );
9743 assert!(
9744 encode_ref_advertisement(&RefAdvertisement {
9745 oid: ObjectId::from_hex(
9746 ObjectFormat::Sha1,
9747 "1111111111111111111111111111111111111111",
9748 )
9749 .expect("test operation should succeed"),
9750 name: "bad ref".into(),
9751 capabilities: Vec::new(),
9752 })
9753 .is_err()
9754 );
9755 assert!(
9756 encode_ref_advertisement_set(&RefAdvertisementSet {
9757 protocol: ProtocolVersion::V2,
9758 refs: Vec::new(),
9759 shallow: Vec::new(),
9760 })
9761 .is_err()
9762 );
9763 assert!(
9764 encode_ref_advertisement_set(&RefAdvertisementSet {
9765 protocol: ProtocolVersion::V0,
9766 refs: Vec::new(),
9767 shallow: vec![
9768 ObjectId::from_hex(
9769 ObjectFormat::Sha1,
9770 "1111111111111111111111111111111111111111",
9771 )
9772 .expect("test operation should succeed")
9773 ],
9774 })
9775 .is_err()
9776 );
9777 }
9778
9779 #[test]
9780 fn dumb_http_info_refs_parse_and_encode_records() {
9781 let main = ObjectId::from_hex(
9782 ObjectFormat::Sha1,
9783 "1111111111111111111111111111111111111111",
9784 )
9785 .expect("test operation should succeed");
9786 let tag = ObjectId::from_hex(
9787 ObjectFormat::Sha1,
9788 "2222222222222222222222222222222222222222",
9789 )
9790 .expect("test operation should succeed");
9791 let peeled = ObjectId::from_hex(
9792 ObjectFormat::Sha1,
9793 "3333333333333333333333333333333333333333",
9794 )
9795 .expect("test operation should succeed");
9796 let input = b"1111111111111111111111111111111111111111\trefs/heads/main\n2222222222222222222222222222222222222222\trefs/tags/v1.0\n3333333333333333333333333333333333333333\trefs/tags/v1.0^{}\n";
9797
9798 let records = parse_dumb_http_info_refs(ObjectFormat::Sha1, input)
9799 .expect("test operation should succeed");
9800 assert_eq!(
9801 records,
9802 vec![
9803 DumbHttpRefRecord {
9804 oid: main,
9805 name: "refs/heads/main".into(),
9806 peeled: false,
9807 },
9808 DumbHttpRefRecord {
9809 oid: tag,
9810 name: "refs/tags/v1.0".into(),
9811 peeled: false,
9812 },
9813 DumbHttpRefRecord {
9814 oid: peeled,
9815 name: "refs/tags/v1.0".into(),
9816 peeled: true,
9817 },
9818 ]
9819 );
9820 assert_eq!(
9821 encode_dumb_http_info_refs(&records).expect("test operation should succeed"),
9822 input
9823 );
9824 assert_eq!(
9825 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"")
9826 .expect("test operation should succeed"),
9827 Vec::<DumbHttpRefRecord>::new()
9828 );
9829 }
9830
9831 #[test]
9832 fn dumb_http_info_refs_streams_round_trip() {
9833 let records = vec![DumbHttpRefRecord {
9834 oid: ObjectId::from_hex(
9835 ObjectFormat::Sha1,
9836 "1111111111111111111111111111111111111111",
9837 )
9838 .expect("test operation should succeed"),
9839 name: "refs/heads/main".into(),
9840 peeled: false,
9841 }];
9842 let mut encoded = Vec::new();
9843 write_dumb_http_info_refs(&mut encoded, &records).expect("test operation should succeed");
9844 let mut input = encoded.as_slice();
9845 assert_eq!(
9846 read_dumb_http_info_refs(ObjectFormat::Sha1, &mut input)
9847 .expect("test operation should succeed"),
9848 records
9849 );
9850 assert!(input.is_empty());
9851 }
9852
9853 #[test]
9854 fn dumb_http_info_refs_reject_malformed_records() {
9855 assert!(
9856 parse_dumb_http_info_refs(
9857 ObjectFormat::Sha1,
9858 b"1111111111111111111111111111111111111111 refs/heads/main\n",
9859 )
9860 .is_err()
9861 );
9862 assert!(
9863 parse_dumb_http_info_refs(
9864 ObjectFormat::Sha1,
9865 b"1111111111111111111111111111111111111111\trefs/heads/main",
9866 )
9867 .is_err()
9868 );
9869 assert!(
9870 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"not-an-oid\trefs/heads/main\n")
9871 .is_err()
9872 );
9873 assert!(
9874 parse_dumb_http_info_refs(
9875 ObjectFormat::Sha1,
9876 b"1111111111111111111111111111111111111111\tbad ref\n",
9877 )
9878 .is_err()
9879 );
9880 assert!(
9881 encode_dumb_http_info_refs(&[DumbHttpRefRecord {
9882 oid: ObjectId::from_hex(
9883 ObjectFormat::Sha1,
9884 "1111111111111111111111111111111111111111",
9885 )
9886 .expect("test operation should succeed"),
9887 name: "refs/tags/v1.0^{}".into(),
9888 peeled: false,
9889 }])
9890 .is_err()
9891 );
9892 }
9893
9894 #[test]
9895 fn dumb_http_alternates_parse_and_encode_locations() {
9896 let input = b"https://example.com/base.git/objects/\n../other.git/objects/\n";
9897 let alternates = parse_dumb_http_alternates(input).expect("test operation should succeed");
9898 assert_eq!(
9899 alternates,
9900 vec![
9901 "https://example.com/base.git/objects/".to_string(),
9902 "../other.git/objects/".to_string(),
9903 ]
9904 );
9905 assert_eq!(
9906 encode_dumb_http_alternates(&alternates).expect("test operation should succeed"),
9907 input
9908 );
9909 assert_eq!(
9910 parse_dumb_http_alternates(b"").expect("test operation should succeed"),
9911 Vec::<String>::new()
9912 );
9913 }
9914
9915 #[test]
9916 fn dumb_http_alternates_streams_round_trip() {
9917 let alternates = vec!["https://example.com/base.git/objects/".to_string()];
9918 let mut encoded = Vec::new();
9919 write_dumb_http_alternates(&mut encoded, &alternates)
9920 .expect("test operation should succeed");
9921 let mut input = encoded.as_slice();
9922 assert_eq!(
9923 read_dumb_http_alternates(&mut input).expect("test operation should succeed"),
9924 alternates
9925 );
9926 assert!(input.is_empty());
9927 }
9928
9929 #[test]
9930 fn dumb_http_alternates_reject_malformed_lines() {
9931 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/").is_err());
9932 assert!(parse_dumb_http_alternates(b"\n").is_err());
9933 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/\r\n").is_err());
9934 assert!(encode_dumb_http_alternates(&["bad\nalternate".to_string()]).is_err());
9935 }
9936
9937 #[test]
9938 fn dumb_http_packs_parse_and_encode_pack_records() {
9939 let first = ObjectId::from_hex(
9940 ObjectFormat::Sha1,
9941 "1111111111111111111111111111111111111111",
9942 )
9943 .expect("test operation should succeed");
9944 let second = ObjectId::from_hex(
9945 ObjectFormat::Sha1,
9946 "2222222222222222222222222222222222222222",
9947 )
9948 .expect("test operation should succeed");
9949 let input = b"P pack-1111111111111111111111111111111111111111.pack\nP pack-2222222222222222222222222222222222222222.pack\n";
9950 let records = parse_dumb_http_packs(ObjectFormat::Sha1, input)
9951 .expect("test operation should succeed");
9952 assert_eq!(
9953 records,
9954 vec![
9955 DumbHttpPackRecord { hash: first },
9956 DumbHttpPackRecord { hash: second },
9957 ]
9958 );
9959 assert_eq!(
9960 encode_dumb_http_packs(&records).expect("test operation should succeed"),
9961 input
9962 );
9963 assert_eq!(
9964 parse_dumb_http_packs(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
9965 Vec::<DumbHttpPackRecord>::new()
9966 );
9967 }
9968
9969 #[test]
9970 fn dumb_http_packs_streams_round_trip() {
9971 let records = vec![DumbHttpPackRecord {
9972 hash: ObjectId::from_hex(
9973 ObjectFormat::Sha1,
9974 "1111111111111111111111111111111111111111",
9975 )
9976 .expect("test operation should succeed"),
9977 }];
9978 let mut encoded = Vec::new();
9979 write_dumb_http_packs(&mut encoded, &records).expect("test operation should succeed");
9980 let mut input = encoded.as_slice();
9981 assert_eq!(
9982 read_dumb_http_packs(ObjectFormat::Sha1, &mut input)
9983 .expect("test operation should succeed"),
9984 records
9985 );
9986 assert!(input.is_empty());
9987 }
9988
9989 #[test]
9990 fn dumb_http_packs_reject_malformed_records() {
9991 assert!(
9992 parse_dumb_http_packs(
9993 ObjectFormat::Sha1,
9994 b"P pack-1111111111111111111111111111111111111111.pack",
9995 )
9996 .is_err()
9997 );
9998 assert!(
9999 parse_dumb_http_packs(
10000 ObjectFormat::Sha1,
10001 b"pack-1111111111111111111111111111111111111111.pack\n",
10002 )
10003 .is_err()
10004 );
10005 assert!(parse_dumb_http_packs(ObjectFormat::Sha1, b"P pack-not-a-hash.pack\n",).is_err());
10006 assert!(
10007 parse_dumb_http_packs(
10008 ObjectFormat::Sha1,
10009 b"P pack-1111111111111111111111111111111111111111.idx\n",
10010 )
10011 .is_err()
10012 );
10013 }
10014
10015 #[test]
10016 fn upload_pack_features_parse_encode_and_validate_request() {
10017 let capabilities = vec![
10018 Capability {
10019 name: "multi_ack".into(),
10020 value: None,
10021 },
10022 Capability {
10023 name: "multi_ack_detailed".into(),
10024 value: None,
10025 },
10026 Capability {
10027 name: "no-done".into(),
10028 value: None,
10029 },
10030 Capability {
10031 name: "thin-pack".into(),
10032 value: None,
10033 },
10034 Capability {
10035 name: "side-band-64k".into(),
10036 value: None,
10037 },
10038 Capability {
10039 name: "ofs-delta".into(),
10040 value: None,
10041 },
10042 Capability {
10043 name: "shallow".into(),
10044 value: None,
10045 },
10046 Capability {
10047 name: "deepen-since".into(),
10048 value: None,
10049 },
10050 Capability {
10051 name: "deepen-not".into(),
10052 value: None,
10053 },
10054 Capability {
10055 name: "include-tag".into(),
10056 value: None,
10057 },
10058 Capability {
10059 name: "no-progress".into(),
10060 value: None,
10061 },
10062 Capability {
10063 name: "filter".into(),
10064 value: None,
10065 },
10066 Capability {
10067 name: "agent".into(),
10068 value: Some("git/2.54.0".into()),
10069 },
10070 Capability {
10071 name: "object-format".into(),
10072 value: Some("sha256".into()),
10073 },
10074 Capability {
10075 name: "symref".into(),
10076 value: Some("HEAD:refs/heads/main".into()),
10077 },
10078 Capability {
10079 name: "custom".into(),
10080 value: Some("value".into()),
10081 },
10082 ];
10083 let features =
10084 parse_upload_pack_features(&capabilities).expect("test operation should succeed");
10085 assert_eq!(
10086 features,
10087 UploadPackFeatures {
10088 multi_ack: true,
10089 multi_ack_detailed: true,
10090 no_done: true,
10091 thin_pack: true,
10092 side_band_64k: true,
10093 ofs_delta: true,
10094 shallow: true,
10095 deepen_since: true,
10096 deepen_not: true,
10097 include_tag: true,
10098 no_progress: true,
10099 filter: true,
10100 agent: Some("git/2.54.0".into()),
10101 object_format: Some(ObjectFormat::Sha256),
10102 symrefs: vec!["HEAD:refs/heads/main".into()],
10103 unknown: vec![Capability {
10104 name: "custom".into(),
10105 value: Some("value".into()),
10106 }],
10107 ..UploadPackFeatures::default()
10108 }
10109 );
10110 assert_eq!(
10111 encode_upload_pack_features(&features).expect("test operation should succeed"),
10112 capabilities
10113 );
10114
10115 let request = UploadPackRequest {
10116 wants: vec![
10117 ObjectId::from_hex(
10118 ObjectFormat::Sha1,
10119 "1111111111111111111111111111111111111111",
10120 )
10121 .expect("test operation should succeed"),
10122 ],
10123 capabilities: vec![
10124 Capability {
10125 name: "multi_ack_detailed".into(),
10126 value: None,
10127 },
10128 Capability {
10129 name: "thin-pack".into(),
10130 value: None,
10131 },
10132 Capability {
10133 name: "side-band-64k".into(),
10134 value: None,
10135 },
10136 Capability {
10137 name: "ofs-delta".into(),
10138 value: None,
10139 },
10140 Capability {
10141 name: "include-tag".into(),
10142 value: None,
10143 },
10144 Capability {
10145 name: "agent".into(),
10146 value: Some("sley".into()),
10147 },
10148 ],
10149 shallow: vec![
10150 ObjectId::from_hex(
10151 ObjectFormat::Sha1,
10152 "2222222222222222222222222222222222222222",
10153 )
10154 .expect("test operation should succeed"),
10155 ],
10156 deepen: Some(5),
10157 deepen_since: Some(1_710_000_000),
10158 deepen_not: vec!["refs/tags/base".into()],
10159 filter: Some("blob:none".into()),
10160 };
10161 validate_upload_pack_request_features(&features, &request)
10162 .expect("test operation should succeed");
10163 }
10164
10165 #[test]
10166 fn upload_pack_features_reject_invalid_requests() {
10167 let want = ObjectId::from_hex(
10168 ObjectFormat::Sha1,
10169 "1111111111111111111111111111111111111111",
10170 )
10171 .expect("test operation should succeed");
10172 let features = UploadPackFeatures {
10173 thin_pack: true,
10174 side_band: true,
10175 ..UploadPackFeatures::default()
10176 };
10177
10178 assert!(
10179 validate_upload_pack_request_features(
10180 &features,
10181 &UploadPackRequest {
10182 wants: vec![want],
10183 capabilities: vec![Capability {
10184 name: "ofs-delta".into(),
10185 value: None,
10186 }],
10187 ..UploadPackRequest::default()
10188 },
10189 )
10190 .is_err()
10191 );
10192 assert!(
10193 validate_upload_pack_request_features(
10194 &features,
10195 &UploadPackRequest {
10196 wants: vec![want],
10197 shallow: vec![want],
10198 ..UploadPackRequest::default()
10199 },
10200 )
10201 .is_err()
10202 );
10203 assert!(
10204 validate_upload_pack_request_features(
10205 &features,
10206 &UploadPackRequest {
10207 wants: vec![want],
10208 filter: Some("blob:none".into()),
10209 ..UploadPackRequest::default()
10210 },
10211 )
10212 .is_err()
10213 );
10214 assert!(
10215 validate_upload_pack_request_features(
10216 &UploadPackFeatures {
10217 side_band: true,
10218 side_band_64k: true,
10219 ..UploadPackFeatures::default()
10220 },
10221 &UploadPackRequest {
10222 wants: vec![want],
10223 capabilities: vec![
10224 Capability {
10225 name: "side-band".into(),
10226 value: None,
10227 },
10228 Capability {
10229 name: "side-band-64k".into(),
10230 value: None,
10231 },
10232 ],
10233 ..UploadPackRequest::default()
10234 },
10235 )
10236 .is_err()
10237 );
10238
10239 assert!(
10240 parse_upload_pack_features(&[
10241 Capability {
10242 name: "thin-pack".into(),
10243 value: None,
10244 },
10245 Capability {
10246 name: "thin-pack".into(),
10247 value: None,
10248 },
10249 ])
10250 .is_err()
10251 );
10252 assert!(
10253 encode_upload_pack_features(&UploadPackFeatures {
10254 unknown: vec![Capability {
10255 name: "filter".into(),
10256 value: None,
10257 }],
10258 ..UploadPackFeatures::default()
10259 })
10260 .is_err()
10261 );
10262 }
10263
10264 #[test]
10265 fn upload_pack_raw_response_builder_filters_unknown_haves_and_builds_pack() {
10266 let want = ObjectId::from_hex(
10267 ObjectFormat::Sha1,
10268 "1111111111111111111111111111111111111111",
10269 )
10270 .expect("test operation should succeed");
10271 let known_have = ObjectId::from_hex(
10272 ObjectFormat::Sha1,
10273 "2222222222222222222222222222222222222222",
10274 )
10275 .expect("test operation should succeed");
10276 let unknown_have = ObjectId::from_hex(
10277 ObjectFormat::Sha1,
10278 "3333333333333333333333333333333333333333",
10279 )
10280 .expect("test operation should succeed");
10281 let existing = std::collections::HashSet::from([want, known_have]);
10282
10283 let response = build_upload_pack_raw_packfile_response(
10284 &UploadPackFeatures::default(),
10285 UploadPackRequest {
10286 wants: vec![want],
10287 ..UploadPackRequest::default()
10288 },
10289 [known_have, unknown_have],
10290 |oid| Ok(existing.contains(oid)),
10291 |wants, haves| {
10292 assert_eq!(wants, vec![want]);
10293 assert_eq!(haves, vec![known_have]);
10294 Ok(Some(b"PACKmock".to_vec()))
10295 },
10296 )
10297 .expect("test operation should succeed");
10298
10299 assert_eq!(
10300 response.acknowledgments,
10301 vec![UploadPackAcknowledgment::Nak]
10302 );
10303 assert_eq!(response.packfile, b"PACKmock");
10304 }
10305
10306 #[test]
10307 fn upload_pack_raw_response_builder_rejects_missing_want_and_empty_pack() {
10308 let want = ObjectId::from_hex(
10309 ObjectFormat::Sha1,
10310 "1111111111111111111111111111111111111111",
10311 )
10312 .expect("test operation should succeed");
10313
10314 assert!(
10315 build_upload_pack_raw_packfile_response(
10316 &UploadPackFeatures::default(),
10317 UploadPackRequest {
10318 wants: vec![want],
10319 ..UploadPackRequest::default()
10320 },
10321 Vec::<ObjectId>::new(),
10322 |_| Ok(false),
10323 |_, _| Ok(Some(b"PACKmock".to_vec())),
10324 )
10325 .is_err()
10326 );
10327
10328 assert!(
10329 build_upload_pack_raw_packfile_response(
10330 &UploadPackFeatures::default(),
10331 UploadPackRequest {
10332 wants: vec![want],
10333 ..UploadPackRequest::default()
10334 },
10335 Vec::<ObjectId>::new(),
10336 |_| Ok(true),
10337 |_, _| Ok(None),
10338 )
10339 .is_err()
10340 );
10341 }
10342
10343 #[test]
10344 fn upload_pack_request_parses_and_encodes_initial_fetch_request() {
10345 let want = ObjectId::from_hex(
10346 ObjectFormat::Sha1,
10347 "1111111111111111111111111111111111111111",
10348 )
10349 .expect("test operation should succeed");
10350 let second_want = ObjectId::from_hex(
10351 ObjectFormat::Sha1,
10352 "2222222222222222222222222222222222222222",
10353 )
10354 .expect("test operation should succeed");
10355 let shallow = ObjectId::from_hex(
10356 ObjectFormat::Sha1,
10357 "3333333333333333333333333333333333333333",
10358 )
10359 .expect("test operation should succeed");
10360 let frames = vec![
10361 PktLineFrame::Data(
10362 b"want 1111111111111111111111111111111111111111 multi_ack thin-pack agent=git/2.54.0\n"
10363 .to_vec(),
10364 ),
10365 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10366 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
10367 PktLineFrame::Data(b"deepen-since 1710000000\n".to_vec()),
10368 PktLineFrame::Data(b"deepen-not refs/tags/base\n".to_vec()),
10369 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10370 PktLineFrame::Flush,
10371 ];
10372 let request = parse_upload_pack_request(ObjectFormat::Sha1, &frames)
10373 .expect("test operation should succeed")
10374 .expect("test operation should succeed");
10375 assert_eq!(
10376 request,
10377 UploadPackRequest {
10378 wants: vec![want, second_want],
10379 capabilities: vec![
10380 Capability {
10381 name: "multi_ack".into(),
10382 value: None,
10383 },
10384 Capability {
10385 name: "thin-pack".into(),
10386 value: None,
10387 },
10388 Capability {
10389 name: "agent".into(),
10390 value: Some("git/2.54.0".into()),
10391 },
10392 ],
10393 shallow: vec![shallow],
10394 deepen: None,
10395 deepen_since: Some(1_710_000_000),
10396 deepen_not: vec!["refs/tags/base".into()],
10397 filter: Some("blob:none".into()),
10398 }
10399 );
10400 assert_eq!(
10401 encode_upload_pack_request(Some(&request)).expect("test operation should succeed"),
10402 frames
10403 );
10404 assert_eq!(
10405 parse_upload_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10406 .expect("test operation should succeed"),
10407 None
10408 );
10409 assert_eq!(
10410 encode_upload_pack_request(None).expect("test operation should succeed"),
10411 vec![PktLineFrame::Flush]
10412 );
10413 }
10414
10415 #[test]
10416 fn upload_pack_request_streams_round_trip() {
10417 let request = UploadPackRequest {
10418 wants: vec![
10419 ObjectId::from_hex(
10420 ObjectFormat::Sha1,
10421 "1111111111111111111111111111111111111111",
10422 )
10423 .expect("test operation should succeed"),
10424 ],
10425 capabilities: vec![Capability {
10426 name: "ofs-delta".into(),
10427 value: None,
10428 }],
10429 deepen: Some(10),
10430 ..UploadPackRequest::default()
10431 };
10432 let mut encoded = Vec::new();
10433 write_upload_pack_request(&mut encoded, Some(&request))
10434 .expect("test operation should succeed");
10435 encoded.extend_from_slice(b"tail");
10436
10437 let mut input = encoded.as_slice();
10438 assert_eq!(
10439 read_upload_pack_request(ObjectFormat::Sha1, &mut input)
10440 .expect("test operation should succeed"),
10441 Some(request)
10442 );
10443 assert_eq!(input, b"tail");
10444 }
10445
10446 #[test]
10447 fn upload_pack_request_rejects_malformed_requests() {
10448 assert!(
10449 parse_upload_pack_request(
10450 ObjectFormat::Sha1,
10451 &[PktLineFrame::Data(
10452 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10453 )],
10454 )
10455 .is_err()
10456 );
10457 assert!(
10458 parse_upload_pack_request(
10459 ObjectFormat::Sha1,
10460 &[
10461 PktLineFrame::Data(
10462 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10463 ),
10464 PktLineFrame::Flush,
10465 ],
10466 )
10467 .is_err()
10468 );
10469 assert!(
10470 parse_upload_pack_request(
10471 ObjectFormat::Sha1,
10472 &[
10473 PktLineFrame::Data(
10474 b"want 1111111111111111111111111111111111111111 thin-pack\n".to_vec(),
10475 ),
10476 PktLineFrame::Data(
10477 b"want 2222222222222222222222222222222222222222 ofs-delta\n".to_vec(),
10478 ),
10479 PktLineFrame::Flush,
10480 ],
10481 )
10482 .is_err()
10483 );
10484 assert!(parse_upload_pack_request(
10485 ObjectFormat::Sha1,
10486 &[
10487 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10488 PktLineFrame::Data(b"deepen 1\n".to_vec()),
10489 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10490 PktLineFrame::Flush,
10491 ],
10492 )
10493 .is_err());
10494 assert!(parse_upload_pack_request(
10495 ObjectFormat::Sha1,
10496 &[
10497 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10498 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10499 PktLineFrame::Data(b"filter tree:0\n".to_vec()),
10500 PktLineFrame::Flush,
10501 ],
10502 )
10503 .is_err());
10504 assert!(encode_upload_pack_request(Some(&UploadPackRequest::default())).is_err());
10505 assert!(
10506 encode_upload_pack_request(Some(&UploadPackRequest {
10507 wants: vec![
10508 ObjectId::from_hex(
10509 ObjectFormat::Sha1,
10510 "1111111111111111111111111111111111111111",
10511 )
10512 .expect("test operation should succeed")
10513 ],
10514 deepen: Some(0),
10515 ..UploadPackRequest::default()
10516 }))
10517 .is_err()
10518 );
10519 }
10520
10521 #[test]
10522 fn upload_pack_shallow_update_parses_and_encodes_records() {
10523 let shallow = ObjectId::from_hex(
10524 ObjectFormat::Sha1,
10525 "1111111111111111111111111111111111111111",
10526 )
10527 .expect("test operation should succeed");
10528 let unshallow = ObjectId::from_hex(
10529 ObjectFormat::Sha1,
10530 "2222222222222222222222222222222222222222",
10531 )
10532 .expect("test operation should succeed");
10533 let frames = vec![
10534 PktLineFrame::Data(b"shallow 1111111111111111111111111111111111111111\n".to_vec()),
10535 PktLineFrame::Data(b"unshallow 2222222222222222222222222222222222222222\n".to_vec()),
10536 PktLineFrame::Flush,
10537 ];
10538 let entries = parse_upload_pack_shallow_update(ObjectFormat::Sha1, &frames)
10539 .expect("test operation should succeed");
10540 assert_eq!(
10541 entries,
10542 vec![
10543 ProtocolV2FetchShallowInfo::Shallow(shallow),
10544 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
10545 ]
10546 );
10547 assert_eq!(
10548 encode_upload_pack_shallow_update(&entries).expect("test operation should succeed"),
10549 frames
10550 );
10551 assert_eq!(
10552 parse_upload_pack_shallow_update(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10553 .expect("test operation should succeed"),
10554 Vec::<ProtocolV2FetchShallowInfo>::new()
10555 );
10556 }
10557
10558 #[test]
10559 fn upload_pack_shallow_update_streams_round_trip() {
10560 let entries = vec![ProtocolV2FetchShallowInfo::Shallow(
10561 ObjectId::from_hex(
10562 ObjectFormat::Sha1,
10563 "1111111111111111111111111111111111111111",
10564 )
10565 .expect("test operation should succeed"),
10566 )];
10567 let mut encoded = Vec::new();
10568 write_upload_pack_shallow_update(&mut encoded, &entries)
10569 .expect("test operation should succeed");
10570 encoded.extend_from_slice(b"tail");
10571
10572 let mut input = encoded.as_slice();
10573 assert_eq!(
10574 read_upload_pack_shallow_update(ObjectFormat::Sha1, &mut input)
10575 .expect("test operation should succeed"),
10576 entries
10577 );
10578 assert_eq!(input, b"tail");
10579 }
10580
10581 #[test]
10582 fn upload_pack_shallow_update_rejects_malformed_records() {
10583 assert!(
10584 parse_upload_pack_shallow_update(
10585 ObjectFormat::Sha1,
10586 &[PktLineFrame::Data(
10587 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10588 )],
10589 )
10590 .is_err()
10591 );
10592 assert!(
10593 parse_upload_pack_shallow_update(
10594 ObjectFormat::Sha1,
10595 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10596 )
10597 .is_err()
10598 );
10599 assert!(
10600 parse_upload_pack_shallow_update(
10601 ObjectFormat::Sha1,
10602 &[
10603 PktLineFrame::Data(
10604 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10605 ),
10606 PktLineFrame::Flush,
10607 PktLineFrame::Data(
10608 b"unshallow 2222222222222222222222222222222222222222\n".to_vec(),
10609 ),
10610 ],
10611 )
10612 .is_err()
10613 );
10614 assert!(
10615 parse_upload_pack_shallow_update(
10616 ObjectFormat::Sha1,
10617 &[
10618 PktLineFrame::Data(
10619 b"unsupported 1111111111111111111111111111111111111111\n".to_vec(),
10620 ),
10621 PktLineFrame::Flush,
10622 ],
10623 )
10624 .is_err()
10625 );
10626 }
10627
10628 #[test]
10629 fn upload_pack_negotiation_request_parses_flush_and_done_rounds() {
10630 let have = ObjectId::from_hex(
10631 ObjectFormat::Sha1,
10632 "1111111111111111111111111111111111111111",
10633 )
10634 .expect("test operation should succeed");
10635 let second_have = ObjectId::from_hex(
10636 ObjectFormat::Sha1,
10637 "2222222222222222222222222222222222222222",
10638 )
10639 .expect("test operation should succeed");
10640 let flush_round = vec![
10641 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10642 PktLineFrame::Data(b"have 2222222222222222222222222222222222222222\n".to_vec()),
10643 PktLineFrame::Flush,
10644 ];
10645 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &flush_round)
10646 .expect("test operation should succeed");
10647 assert_eq!(
10648 request,
10649 UploadPackNegotiationRequest {
10650 haves: vec![have, second_have],
10651 done: false,
10652 }
10653 );
10654 assert_eq!(
10655 encode_upload_pack_negotiation_request(&request)
10656 .expect("test operation should succeed"),
10657 flush_round
10658 );
10659
10660 let done_round = vec![
10661 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10662 PktLineFrame::Data(b"done\n".to_vec()),
10663 ];
10664 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &done_round)
10665 .expect("test operation should succeed");
10666 assert_eq!(
10667 request,
10668 UploadPackNegotiationRequest {
10669 haves: vec![have],
10670 done: true,
10671 }
10672 );
10673 assert_eq!(
10674 encode_upload_pack_negotiation_request(&request)
10675 .expect("test operation should succeed"),
10676 done_round
10677 );
10678 }
10679
10680 #[test]
10681 fn upload_pack_negotiation_request_streams_round_trip() {
10682 let first = UploadPackNegotiationRequest {
10683 haves: vec![
10684 ObjectId::from_hex(
10685 ObjectFormat::Sha1,
10686 "1111111111111111111111111111111111111111",
10687 )
10688 .expect("test operation should succeed"),
10689 ],
10690 done: false,
10691 };
10692 let second = UploadPackNegotiationRequest {
10693 haves: Vec::new(),
10694 done: true,
10695 };
10696 let mut encoded = Vec::new();
10697 write_upload_pack_negotiation_request(&mut encoded, &first)
10698 .expect("test operation should succeed");
10699 write_upload_pack_negotiation_request(&mut encoded, &second)
10700 .expect("test operation should succeed");
10701 encoded.extend_from_slice(b"tail");
10702
10703 let mut input = encoded.as_slice();
10704 assert_eq!(
10705 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10706 .expect("test operation should succeed"),
10707 first
10708 );
10709 assert_eq!(
10710 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10711 .expect("test operation should succeed"),
10712 second
10713 );
10714 assert_eq!(input, b"tail");
10715 }
10716
10717 #[test]
10718 fn upload_pack_negotiation_request_rejects_malformed_rounds() {
10719 assert!(
10720 parse_upload_pack_negotiation_request(
10721 ObjectFormat::Sha1,
10722 &[PktLineFrame::Data(
10723 b"have 1111111111111111111111111111111111111111\n".to_vec(),
10724 )],
10725 )
10726 .is_err()
10727 );
10728 assert!(
10729 parse_upload_pack_negotiation_request(
10730 ObjectFormat::Sha1,
10731 &[PktLineFrame::Data(
10732 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10733 )],
10734 )
10735 .is_err()
10736 );
10737 assert!(parse_upload_pack_negotiation_request(
10738 ObjectFormat::Sha1,
10739 &[
10740 PktLineFrame::Data(b"done\n".to_vec()),
10741 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec(),),
10742 ],
10743 )
10744 .is_err());
10745 assert!(
10746 parse_upload_pack_negotiation_request(
10747 ObjectFormat::Sha1,
10748 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10749 )
10750 .is_err()
10751 );
10752 }
10753
10754 #[test]
10755 fn upload_pack_acknowledgments_parse_and_encode_statuses() {
10756 let oid = ObjectId::from_hex(
10757 ObjectFormat::Sha1,
10758 "1111111111111111111111111111111111111111",
10759 )
10760 .expect("test operation should succeed");
10761 assert_eq!(
10762 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"NAK\n")
10763 .expect("test operation should succeed"),
10764 UploadPackAcknowledgment::Nak
10765 );
10766 for (payload, status) in [
10767 (
10768 b"ACK 1111111111111111111111111111111111111111\n".as_slice(),
10769 None,
10770 ),
10771 (
10772 b"ACK 1111111111111111111111111111111111111111 continue\n".as_slice(),
10773 Some(UploadPackAckStatus::Continue),
10774 ),
10775 (
10776 b"ACK 1111111111111111111111111111111111111111 common\n".as_slice(),
10777 Some(UploadPackAckStatus::Common),
10778 ),
10779 (
10780 b"ACK 1111111111111111111111111111111111111111 ready\n".as_slice(),
10781 Some(UploadPackAckStatus::Ready),
10782 ),
10783 ] {
10784 let acknowledgment = parse_upload_pack_acknowledgment(ObjectFormat::Sha1, payload)
10785 .expect("test operation should succeed");
10786 assert_eq!(
10787 acknowledgment,
10788 UploadPackAcknowledgment::Ack { oid, status }
10789 );
10790 assert_eq!(
10791 encode_upload_pack_acknowledgment(&acknowledgment)
10792 .expect("test operation should succeed"),
10793 payload
10794 );
10795 }
10796 }
10797
10798 #[test]
10799 fn upload_pack_acknowledgments_stream_round_trip_and_reject_bad_lines() {
10800 let acknowledgment = UploadPackAcknowledgment::Ack {
10801 oid: ObjectId::from_hex(
10802 ObjectFormat::Sha1,
10803 "1111111111111111111111111111111111111111",
10804 )
10805 .expect("test operation should succeed"),
10806 status: Some(UploadPackAckStatus::Ready),
10807 };
10808 let mut encoded = Vec::new();
10809 write_upload_pack_acknowledgment(&mut encoded, &acknowledgment)
10810 .expect("test operation should succeed");
10811 encoded.extend_from_slice(b"tail");
10812
10813 let mut input = encoded.as_slice();
10814 assert_eq!(
10815 read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut input)
10816 .expect("test operation should succeed"),
10817 acknowledgment
10818 );
10819 assert_eq!(input, b"tail");
10820 assert!(parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ACK not-an-oid\n").is_err());
10821 assert!(
10822 parse_upload_pack_acknowledgment(
10823 ObjectFormat::Sha1,
10824 b"ACK 1111111111111111111111111111111111111111 unknown\n",
10825 )
10826 .is_err()
10827 );
10828 assert!(
10829 parse_upload_pack_acknowledgment(
10830 ObjectFormat::Sha1,
10831 b"ACK 1111111111111111111111111111111111111111 ready extra\n",
10832 )
10833 .is_err()
10834 );
10835 assert!(
10836 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ERR remote died\n").is_err()
10837 );
10838 assert!(read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut &b"0000"[..]).is_err());
10839 }
10840
10841 #[test]
10842 fn upload_pack_packfile_response_parses_acknowledgments_and_sideband() {
10843 let oid = ObjectId::from_hex(
10844 ObjectFormat::Sha1,
10845 "1111111111111111111111111111111111111111",
10846 )
10847 .expect("test operation should succeed");
10848 let frames = vec![
10849 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()),
10850 PktLineFrame::Data(b"NAK\n".to_vec()),
10851 PktLineFrame::Data(b"\x01PACK".to_vec()),
10852 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
10853 PktLineFrame::Data(b"\x01 bytes".to_vec()),
10854 PktLineFrame::Flush,
10855 ];
10856 let response = parse_upload_pack_packfile_response(ObjectFormat::Sha1, &frames)
10857 .expect("test operation should succeed");
10858 assert_eq!(
10859 response,
10860 UploadPackPackfileResponse {
10861 acknowledgments: vec![
10862 UploadPackAcknowledgment::Ack {
10863 oid,
10864 status: Some(UploadPackAckStatus::Common),
10865 },
10866 UploadPackAcknowledgment::Nak,
10867 ],
10868 sideband: vec![
10869 SideBandPacket {
10870 channel: SideBandChannel::Data,
10871 data: b"PACK".to_vec(),
10872 },
10873 SideBandPacket {
10874 channel: SideBandChannel::Progress,
10875 data: b"counting objects\n".to_vec(),
10876 },
10877 SideBandPacket {
10878 channel: SideBandChannel::Data,
10879 data: b" bytes".to_vec(),
10880 },
10881 ],
10882 }
10883 );
10884 assert_eq!(
10885 demux_upload_pack_packfile_response(&response).expect("test operation should succeed"),
10886 SideBandDemux {
10887 data: b"PACK bytes".to_vec(),
10888 progress: vec![b"counting objects\n".to_vec()],
10889 }
10890 );
10891 assert_eq!(
10892 encode_upload_pack_packfile_response(&response).expect("test operation should succeed"),
10893 frames
10894 );
10895 }
10896
10897 #[test]
10898 fn upload_pack_packfile_response_streams_round_trip() {
10899 let response = UploadPackPackfileResponse {
10900 acknowledgments: vec![UploadPackAcknowledgment::Nak],
10901 sideband: vec![SideBandPacket {
10902 channel: SideBandChannel::Data,
10903 data: b"PACK".to_vec(),
10904 }],
10905 };
10906 let mut encoded = Vec::new();
10907 write_upload_pack_packfile_response(&mut encoded, &response)
10908 .expect("test operation should succeed");
10909 encoded.extend_from_slice(b"tail");
10910
10911 let mut input = encoded.as_slice();
10912 assert_eq!(
10913 read_upload_pack_packfile_response(ObjectFormat::Sha1, &mut input)
10914 .expect("test operation should succeed"),
10915 response
10916 );
10917 assert_eq!(input, b"tail");
10918 }
10919
10920 #[test]
10921 fn upload_pack_packfile_response_rejects_malformed_streams() {
10922 assert!(
10923 parse_upload_pack_packfile_response(
10924 ObjectFormat::Sha1,
10925 &[PktLineFrame::Data(b"NAK\n".to_vec())],
10926 )
10927 .is_err()
10928 );
10929 assert!(
10930 parse_upload_pack_packfile_response(
10931 ObjectFormat::Sha1,
10932 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10933 )
10934 .is_err()
10935 );
10936 assert!(
10937 parse_upload_pack_packfile_response(
10938 ObjectFormat::Sha1,
10939 &[
10940 PktLineFrame::Data(b"\x01PACK".to_vec()),
10941 PktLineFrame::Data(
10942 b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()
10943 ),
10944 PktLineFrame::Flush,
10945 ],
10946 )
10947 .is_err()
10948 );
10949 assert!(
10950 parse_upload_pack_packfile_response(
10951 ObjectFormat::Sha1,
10952 &[
10953 PktLineFrame::Data(b"NAK\n".to_vec()),
10954 PktLineFrame::Flush,
10955 PktLineFrame::Data(b"\x01PACK".to_vec()),
10956 ],
10957 )
10958 .is_err()
10959 );
10960 assert!(
10961 parse_upload_pack_packfile_response(
10962 ObjectFormat::Sha1,
10963 &[
10964 PktLineFrame::Data(b"NAK\n".to_vec()),
10965 PktLineFrame::Data(b"\x04bad".to_vec()),
10966 PktLineFrame::Flush,
10967 ],
10968 )
10969 .is_err()
10970 );
10971 }
10972
10973 #[test]
10974 fn upload_pack_raw_packfile_response_parses_acknowledgments_and_raw_pack() {
10975 let oid = ObjectId::from_hex(
10976 ObjectFormat::Sha1,
10977 "1111111111111111111111111111111111111111",
10978 )
10979 .expect("test operation should succeed");
10980 let response = UploadPackRawPackfileResponse {
10981 acknowledgments: vec![
10982 UploadPackAcknowledgment::Ack {
10983 oid,
10984 status: Some(UploadPackAckStatus::Common),
10985 },
10986 UploadPackAcknowledgment::Nak,
10987 ],
10988 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
10989 };
10990 let encoded = encode_upload_pack_raw_packfile_response(&response)
10991 .expect("test operation should succeed");
10992 assert_eq!(
10993 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &encoded)
10994 .expect("test operation should succeed"),
10995 response
10996 );
10997 }
10998
10999 #[test]
11000 fn upload_pack_raw_packfile_response_streams_round_trip() {
11001 let response = UploadPackRawPackfileResponse {
11002 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11003 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11004 };
11005 let mut encoded = Vec::new();
11006 write_upload_pack_raw_packfile_response(&mut encoded, &response)
11007 .expect("test operation should succeed");
11008 assert_eq!(
11009 encoded,
11010 encode_upload_pack_raw_packfile_response(&response)
11011 .expect("test operation should succeed")
11012 );
11013
11014 let mut input = encoded.as_slice();
11015 assert_eq!(
11016 read_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &mut input)
11017 .expect("test operation should succeed"),
11018 response
11019 );
11020 assert!(input.is_empty());
11021 }
11022
11023 #[test]
11024 fn upload_pack_raw_packfile_response_rejects_malformed_streams() {
11025 let ack = PktLineFrame::data(b"NAK\n".to_vec())
11026 .expect("test operation should succeed")
11027 .try_encode()
11028 .expect("test operation should succeed");
11029 let bad_ack = PktLineFrame::data(b"ACK not-an-oid\n".to_vec())
11030 .expect("test operation should succeed")
11031 .try_encode()
11032 .expect("test operation should succeed");
11033 let non_ack =
11034 PktLineFrame::data(b"have 1111111111111111111111111111111111111111\n".to_vec())
11035 .expect("test operation should succeed")
11036 .try_encode()
11037 .expect("test operation should succeed");
11038 let mut garbage_after_ack = ack.clone();
11039 garbage_after_ack.extend_from_slice(b"garbage");
11040
11041 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"").is_err());
11042 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &ack).is_err());
11043 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &bad_ack).is_err());
11044 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"0000PACK").is_err());
11045 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &non_ack).is_err());
11046 assert!(
11047 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &garbage_after_ack)
11048 .is_err()
11049 );
11050 assert!(
11051 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11052 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11053 packfile: Vec::new(),
11054 })
11055 .is_err()
11056 );
11057 assert!(
11058 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11059 acknowledgments: Vec::new(),
11060 packfile: b"not-a-pack".to_vec(),
11061 })
11062 .is_err()
11063 );
11064 }
11065
11066 #[test]
11067 fn upload_pack_request_encodes_deepen_request() {
11068 let want = ObjectId::from_hex(
11073 ObjectFormat::Sha1,
11074 "1111111111111111111111111111111111111111",
11075 )
11076 .expect("test operation should succeed");
11077 let boundary = ObjectId::from_hex(
11078 ObjectFormat::Sha1,
11079 "2222222222222222222222222222222222222222",
11080 )
11081 .expect("test operation should succeed");
11082 let request = UploadPackRequest {
11083 wants: vec![want],
11084 capabilities: vec![Capability {
11085 name: "shallow".into(),
11086 value: None,
11087 }],
11088 shallow: vec![boundary],
11089 deepen: Some(1),
11090 ..UploadPackRequest::default()
11091 };
11092 let mut encoded = Vec::new();
11093 write_upload_pack_request(&mut encoded, Some(&request))
11094 .expect("test operation should succeed");
11095 let mut expected = Vec::new();
11096 expected.extend_from_slice(b"003awant 1111111111111111111111111111111111111111 shallow\n");
11097 expected.extend_from_slice(b"0035shallow 2222222222222222222222222222222222222222\n");
11098 expected.extend_from_slice(b"000ddeepen 1\n");
11099 expected.extend_from_slice(b"0000");
11100 assert_eq!(encoded, expected);
11101 }
11102
11103 #[test]
11104 fn upload_pack_shallow_info_response_parses_shallow_unshallow_and_pack() {
11105 let shallow = ObjectId::from_hex(
11109 ObjectFormat::Sha1,
11110 "1111111111111111111111111111111111111111",
11111 )
11112 .expect("test operation should succeed");
11113 let unshallow = ObjectId::from_hex(
11114 ObjectFormat::Sha1,
11115 "2222222222222222222222222222222222222222",
11116 )
11117 .expect("test operation should succeed");
11118 let mut input = Vec::new();
11119 input.extend_from_slice(b"0035shallow 1111111111111111111111111111111111111111\n");
11120 input.extend_from_slice(b"0037unshallow 2222222222222222222222222222222222222222\n");
11121 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11123 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11124
11125 let (entries, response) =
11126 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11127 .expect("test operation should succeed");
11128 assert_eq!(
11129 entries,
11130 vec![
11131 ProtocolV2FetchShallowInfo::Shallow(shallow),
11132 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
11133 ]
11134 );
11135 assert_eq!(
11136 response,
11137 UploadPackRawPackfileResponse {
11138 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11139 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11140 }
11141 );
11142
11143 let mut stream = input.as_slice();
11145 let (read_entries, read_response) =
11146 read_upload_pack_shallow_info_and_raw_packfile_response(
11147 ObjectFormat::Sha1,
11148 &mut stream,
11149 )
11150 .expect("test operation should succeed");
11151 assert_eq!(read_entries, entries);
11152 assert_eq!(read_response, response);
11153 }
11154
11155 #[test]
11156 fn upload_pack_shallow_info_response_handles_empty_shallow_section() {
11157 let mut input = Vec::new();
11160 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11162 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11163
11164 let (entries, response) =
11165 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11166 .expect("test operation should succeed");
11167 assert!(entries.is_empty());
11168 assert_eq!(
11169 response.acknowledgments,
11170 vec![UploadPackAcknowledgment::Nak]
11171 );
11172 assert!(response.packfile.starts_with(b"PACK"));
11173 }
11174
11175 #[test]
11176 fn upload_pack_shallow_info_response_rejects_malformed_sections() {
11177 let truncated = b"0035shallow 1111111111111111111111111111111111111111\n".to_vec();
11179 assert!(
11180 parse_upload_pack_shallow_info_and_raw_packfile_response(
11181 ObjectFormat::Sha1,
11182 &truncated
11183 )
11184 .is_err()
11185 );
11186 let mut delimiter_section = Vec::new();
11188 delimiter_section.extend_from_slice(b"0001"); assert!(
11190 parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &delimiter_section).is_err()
11191 );
11192 let mut bad_line = Vec::new();
11194 bad_line.extend_from_slice(b"0008NAK\n");
11195 assert!(parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &bad_line).is_err());
11196 let mut no_pack = Vec::new();
11198 no_pack.extend_from_slice(b"0000"); no_pack.extend_from_slice(b"0008NAK\n");
11200 assert!(
11201 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &no_pack)
11202 .is_err()
11203 );
11204 }
11205
11206 #[test]
11207 fn receive_pack_request_parses_and_encodes_commands() {
11208 let old_id = ObjectId::from_hex(
11209 ObjectFormat::Sha1,
11210 "1111111111111111111111111111111111111111",
11211 )
11212 .expect("test operation should succeed");
11213 let new_id = ObjectId::from_hex(
11214 ObjectFormat::Sha1,
11215 "2222222222222222222222222222222222222222",
11216 )
11217 .expect("test operation should succeed");
11218 let delete_old_id = ObjectId::from_hex(
11219 ObjectFormat::Sha1,
11220 "3333333333333333333333333333333333333333",
11221 )
11222 .expect("test operation should succeed");
11223 let zero = ObjectId::from_hex(
11224 ObjectFormat::Sha1,
11225 "0000000000000000000000000000000000000000",
11226 )
11227 .expect("test operation should succeed");
11228 let shallow = ObjectId::from_hex(
11229 ObjectFormat::Sha1,
11230 "4444444444444444444444444444444444444444",
11231 )
11232 .expect("test operation should succeed");
11233 let frames = vec![
11234 PktLineFrame::Data(b"shallow 4444444444444444444444444444444444444444\n".to_vec()),
11235 PktLineFrame::Data(
11236 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status side-band-64k agent=git/2.54.0\n"
11237 .to_vec(),
11238 ),
11239 PktLineFrame::Data(
11240 b"3333333333333333333333333333333333333333 0000000000000000000000000000000000000000 refs/heads/old\n"
11241 .to_vec(),
11242 ),
11243 PktLineFrame::Flush,
11244 ];
11245 let request = parse_receive_pack_request(ObjectFormat::Sha1, &frames)
11246 .expect("test operation should succeed");
11247 assert_eq!(
11248 request,
11249 ReceivePackRequest {
11250 shallow: vec![shallow],
11251 commands: vec![
11252 ReceivePackCommand {
11253 old_id,
11254 new_id,
11255 name: "refs/heads/main".into(),
11256 },
11257 ReceivePackCommand {
11258 old_id: delete_old_id,
11259 new_id: zero,
11260 name: "refs/heads/old".into(),
11261 },
11262 ],
11263 capabilities: vec![
11264 Capability {
11265 name: "report-status".into(),
11266 value: None,
11267 },
11268 Capability {
11269 name: "side-band-64k".into(),
11270 value: None,
11271 },
11272 Capability {
11273 name: "agent".into(),
11274 value: Some("git/2.54.0".into()),
11275 },
11276 ],
11277 }
11278 );
11279 assert_eq!(
11280 encode_receive_pack_request(&request).expect("test operation should succeed"),
11281 frames
11282 );
11283 assert_eq!(
11284 parse_receive_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
11285 .expect("test operation should succeed"),
11286 ReceivePackRequest::default()
11287 );
11288 }
11289
11290 #[test]
11291 fn receive_pack_request_streams_round_trip() {
11292 let request = ReceivePackRequest {
11293 commands: vec![ReceivePackCommand {
11294 old_id: ObjectId::from_hex(
11295 ObjectFormat::Sha1,
11296 "0000000000000000000000000000000000000000",
11297 )
11298 .expect("test operation should succeed"),
11299 new_id: ObjectId::from_hex(
11300 ObjectFormat::Sha1,
11301 "1111111111111111111111111111111111111111",
11302 )
11303 .expect("test operation should succeed"),
11304 name: "refs/heads/main".into(),
11305 }],
11306 capabilities: vec![Capability {
11307 name: "report-status".into(),
11308 value: None,
11309 }],
11310 ..ReceivePackRequest::default()
11311 };
11312 let mut encoded = Vec::new();
11313 write_receive_pack_request(&mut encoded, &request).expect("test operation should succeed");
11314 encoded.extend_from_slice(b"PACK");
11315
11316 let mut input = encoded.as_slice();
11317 assert_eq!(
11318 read_receive_pack_request(ObjectFormat::Sha1, &mut input)
11319 .expect("test operation should succeed"),
11320 request
11321 );
11322 assert_eq!(input, b"PACK");
11323 }
11324
11325 #[test]
11326 fn receive_pack_request_rejects_malformed_commands() {
11327 assert!(
11328 parse_receive_pack_request(
11329 ObjectFormat::Sha1,
11330 &[PktLineFrame::Data(
11331 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11332 .to_vec(),
11333 )],
11334 )
11335 .is_err()
11336 );
11337 assert!(
11338 parse_receive_pack_request(
11339 ObjectFormat::Sha1,
11340 &[
11341 PktLineFrame::Data(
11342 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11343 .to_vec(),
11344 ),
11345 PktLineFrame::Data(
11346 b"shallow 3333333333333333333333333333333333333333\n".to_vec(),
11347 ),
11348 PktLineFrame::Flush,
11349 ],
11350 )
11351 .is_err()
11352 );
11353 assert!(
11354 parse_receive_pack_request(
11355 ObjectFormat::Sha1,
11356 &[
11357 PktLineFrame::Data(
11358 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status\n"
11359 .to_vec(),
11360 ),
11361 PktLineFrame::Data(
11362 b"3333333333333333333333333333333333333333 4444444444444444444444444444444444444444 refs/heads/next\0side-band-64k\n"
11363 .to_vec(),
11364 ),
11365 PktLineFrame::Flush,
11366 ],
11367 )
11368 .is_err()
11369 );
11370 assert!(
11371 parse_receive_pack_request(
11372 ObjectFormat::Sha1,
11373 &[
11374 PktLineFrame::Data(
11375 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
11376 ),
11377 PktLineFrame::Flush,
11378 ],
11379 )
11380 .is_err()
11381 );
11382 assert!(
11383 encode_receive_pack_request(&ReceivePackRequest {
11384 shallow: vec![
11385 ObjectId::from_hex(
11386 ObjectFormat::Sha1,
11387 "1111111111111111111111111111111111111111",
11388 )
11389 .expect("test operation should succeed")
11390 ],
11391 ..ReceivePackRequest::default()
11392 })
11393 .is_err()
11394 );
11395 assert!(
11396 encode_receive_pack_request(&ReceivePackRequest {
11397 commands: vec![ReceivePackCommand {
11398 old_id: ObjectId::from_hex(
11399 ObjectFormat::Sha1,
11400 "1111111111111111111111111111111111111111",
11401 )
11402 .expect("test operation should succeed"),
11403 new_id: ObjectId::from_hex(
11404 ObjectFormat::Sha1,
11405 "2222222222222222222222222222222222222222",
11406 )
11407 .expect("test operation should succeed"),
11408 name: "bad ref".into(),
11409 }],
11410 ..ReceivePackRequest::default()
11411 })
11412 .is_err()
11413 );
11414 }
11415
11416 #[test]
11417 fn receive_pack_features_parse_encode_and_validate_push_request() {
11418 let capabilities = vec![
11419 Capability {
11420 name: "report-status".into(),
11421 value: None,
11422 },
11423 Capability {
11424 name: "report-status-v2".into(),
11425 value: None,
11426 },
11427 Capability {
11428 name: "delete-refs".into(),
11429 value: None,
11430 },
11431 Capability {
11432 name: "ofs-delta".into(),
11433 value: None,
11434 },
11435 Capability {
11436 name: "atomic".into(),
11437 value: None,
11438 },
11439 Capability {
11440 name: "push-options".into(),
11441 value: None,
11442 },
11443 Capability {
11444 name: "side-band-64k".into(),
11445 value: None,
11446 },
11447 Capability {
11448 name: "quiet".into(),
11449 value: None,
11450 },
11451 Capability {
11452 name: "no-thin".into(),
11453 value: None,
11454 },
11455 Capability {
11456 name: "agent".into(),
11457 value: Some("git/2.54.0".into()),
11458 },
11459 Capability {
11460 name: "object-format".into(),
11461 value: Some("sha256".into()),
11462 },
11463 Capability {
11464 name: "custom".into(),
11465 value: Some("value".into()),
11466 },
11467 ];
11468 let features =
11469 parse_receive_pack_features(&capabilities).expect("test operation should succeed");
11470 assert_eq!(
11471 features,
11472 ReceivePackFeatures {
11473 report_status: true,
11474 report_status_v2: true,
11475 delete_refs: true,
11476 ofs_delta: true,
11477 atomic: true,
11478 push_options: true,
11479 side_band_64k: true,
11480 quiet: true,
11481 no_thin: true,
11482 agent: Some("git/2.54.0".into()),
11483 object_format: Some(ObjectFormat::Sha256),
11484 unknown: vec![Capability {
11485 name: "custom".into(),
11486 value: Some("value".into()),
11487 }],
11488 }
11489 );
11490 assert_eq!(
11491 encode_receive_pack_features(&features).expect("test operation should succeed"),
11492 capabilities
11493 );
11494
11495 let request = ReceivePackPushRequest {
11496 commands: ReceivePackRequest {
11497 commands: vec![ReceivePackCommand {
11498 old_id: ObjectId::from_hex(
11499 ObjectFormat::Sha1,
11500 "1111111111111111111111111111111111111111",
11501 )
11502 .expect("test operation should succeed"),
11503 new_id: ObjectId::from_hex(
11504 ObjectFormat::Sha1,
11505 "2222222222222222222222222222222222222222",
11506 )
11507 .expect("test operation should succeed"),
11508 name: "refs/heads/main".into(),
11509 }],
11510 capabilities: vec![
11511 Capability {
11512 name: "report-status".into(),
11513 value: None,
11514 },
11515 Capability {
11516 name: "ofs-delta".into(),
11517 value: None,
11518 },
11519 Capability {
11520 name: "push-options".into(),
11521 value: None,
11522 },
11523 Capability {
11524 name: "side-band-64k".into(),
11525 value: None,
11526 },
11527 Capability {
11528 name: "agent".into(),
11529 value: Some("sley".into()),
11530 },
11531 ],
11532 ..ReceivePackRequest::default()
11533 },
11534 push_options: Some(vec!["ci.skip".into()]),
11535 packfile: b"PACKpayload".to_vec(),
11536 };
11537 validate_receive_pack_push_request_features(&features, &request)
11538 .expect("test operation should succeed");
11539 }
11540
11541 #[test]
11542 fn receive_pack_features_reject_invalid_push_requests() {
11543 let old_id = ObjectId::from_hex(
11544 ObjectFormat::Sha1,
11545 "1111111111111111111111111111111111111111",
11546 )
11547 .expect("test operation should succeed");
11548 let new_id = ObjectId::from_hex(
11549 ObjectFormat::Sha1,
11550 "2222222222222222222222222222222222222222",
11551 )
11552 .expect("test operation should succeed");
11553 let zero = ObjectId::from_hex(
11554 ObjectFormat::Sha1,
11555 "0000000000000000000000000000000000000000",
11556 )
11557 .expect("test operation should succeed");
11558 let features = ReceivePackFeatures {
11559 report_status: true,
11560 push_options: true,
11561 ..ReceivePackFeatures::default()
11562 };
11563 let update = ReceivePackCommand {
11564 old_id: old_id.clone(),
11565 new_id: new_id.clone(),
11566 name: "refs/heads/main".into(),
11567 };
11568
11569 assert!(
11570 validate_receive_pack_push_request_features(
11571 &features,
11572 &ReceivePackPushRequest {
11573 commands: ReceivePackRequest {
11574 commands: vec![update.clone()],
11575 capabilities: vec![Capability {
11576 name: "push-options".into(),
11577 value: None,
11578 }],
11579 ..ReceivePackRequest::default()
11580 },
11581 push_options: None,
11582 packfile: b"PACKpayload".to_vec(),
11583 },
11584 )
11585 .is_err()
11586 );
11587 assert!(
11588 validate_receive_pack_push_request_features(
11589 &features,
11590 &ReceivePackPushRequest {
11591 commands: ReceivePackRequest {
11592 commands: vec![update.clone()],
11593 ..ReceivePackRequest::default()
11594 },
11595 push_options: Some(Vec::new()),
11596 packfile: b"PACKpayload".to_vec(),
11597 },
11598 )
11599 .is_err()
11600 );
11601 assert!(
11602 validate_receive_pack_push_request_features(
11603 &features,
11604 &ReceivePackPushRequest {
11605 commands: ReceivePackRequest {
11606 commands: vec![ReceivePackCommand {
11607 old_id: old_id.clone(),
11608 new_id: zero.clone(),
11609 name: "refs/heads/main".into(),
11610 }],
11611 ..ReceivePackRequest::default()
11612 },
11613 push_options: None,
11614 packfile: Vec::new(),
11615 },
11616 )
11617 .is_err()
11618 );
11619 assert!(
11620 validate_receive_pack_push_request_features(
11621 &features,
11622 &ReceivePackPushRequest {
11623 commands: ReceivePackRequest {
11624 commands: vec![update.clone()],
11625 ..ReceivePackRequest::default()
11626 },
11627 push_options: None,
11628 packfile: Vec::new(),
11629 },
11630 )
11631 .is_err()
11632 );
11633 assert!(
11634 validate_receive_pack_push_request_features(
11635 &ReceivePackFeatures {
11636 delete_refs: true,
11637 ..ReceivePackFeatures::default()
11638 },
11639 &ReceivePackPushRequest {
11640 commands: ReceivePackRequest {
11641 commands: vec![ReceivePackCommand {
11642 old_id,
11643 new_id: zero,
11644 name: "refs/heads/main".into(),
11645 }],
11646 ..ReceivePackRequest::default()
11647 },
11648 push_options: None,
11649 packfile: b"PACKpayload".to_vec(),
11650 },
11651 )
11652 .is_err()
11653 );
11654 assert!(
11655 validate_receive_pack_push_request_features(
11656 &features,
11657 &ReceivePackPushRequest {
11658 commands: ReceivePackRequest {
11659 commands: vec![update],
11660 capabilities: vec![Capability {
11661 name: "atomic".into(),
11662 value: None,
11663 }],
11664 ..ReceivePackRequest::default()
11665 },
11666 push_options: None,
11667 packfile: b"PACKpayload".to_vec(),
11668 },
11669 )
11670 .is_err()
11671 );
11672
11673 assert!(
11674 parse_receive_pack_features(&[
11675 Capability {
11676 name: "push-options".into(),
11677 value: None,
11678 },
11679 Capability {
11680 name: "push-options".into(),
11681 value: None,
11682 },
11683 ])
11684 .is_err()
11685 );
11686 assert!(
11687 encode_receive_pack_features(&ReceivePackFeatures {
11688 unknown: vec![Capability {
11689 name: "atomic".into(),
11690 value: None,
11691 }],
11692 ..ReceivePackFeatures::default()
11693 })
11694 .is_err()
11695 );
11696 }
11697
11698 #[test]
11699 fn receive_pack_apply_helper_installs_pack_verifies_objects_and_reports_ok() {
11700 let old_id = ObjectId::from_hex(
11701 ObjectFormat::Sha1,
11702 "1111111111111111111111111111111111111111",
11703 )
11704 .expect("test operation should succeed");
11705 let new_id = ObjectId::from_hex(
11706 ObjectFormat::Sha1,
11707 "2222222222222222222222222222222222222222",
11708 )
11709 .expect("test operation should succeed");
11710 let request = ReceivePackPushRequest {
11711 commands: ReceivePackRequest {
11712 commands: vec![ReceivePackCommand {
11713 old_id: old_id.clone(),
11714 new_id: new_id.clone(),
11715 name: "refs/heads/main".into(),
11716 }],
11717 ..ReceivePackRequest::default()
11718 },
11719 packfile: b"PACKpayload".to_vec(),
11720 ..ReceivePackPushRequest::default()
11721 };
11722 let installed = std::cell::Cell::new(false);
11723 let applied = std::cell::RefCell::new(Vec::new());
11724
11725 let report = apply_receive_pack_push_request(
11726 &ReceivePackFeatures::default(),
11727 &request,
11728 |_| unreachable!("update stale-old checks belong to the ref transaction callback"),
11729 |packfile| {
11730 assert_eq!(packfile, b"PACKpayload");
11731 installed.set(true);
11732 Ok(())
11733 },
11734 |oid| Ok(oid == &new_id),
11735 |commands| {
11736 applied.borrow_mut().extend_from_slice(commands);
11737 Ok(())
11738 },
11739 |_| unreachable!("no delete command should be applied"),
11740 )
11741 .expect("test operation should succeed");
11742
11743 assert!(installed.get());
11744 assert_eq!(applied.into_inner(), request.commands.commands);
11745 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11746 assert_eq!(
11747 report.commands,
11748 vec![ReceivePackCommandStatus::Ok {
11749 name: "refs/heads/main".into(),
11750 }]
11751 );
11752 }
11753
11754 #[test]
11755 fn receive_pack_apply_helper_preserves_delete_only_and_stale_delete_rules() {
11756 let old_id = ObjectId::from_hex(
11757 ObjectFormat::Sha1,
11758 "1111111111111111111111111111111111111111",
11759 )
11760 .expect("test operation should succeed");
11761 let other_id = ObjectId::from_hex(
11762 ObjectFormat::Sha1,
11763 "2222222222222222222222222222222222222222",
11764 )
11765 .expect("test operation should succeed");
11766 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
11767 let request = ReceivePackPushRequest {
11768 commands: ReceivePackRequest {
11769 commands: vec![ReceivePackCommand {
11770 old_id: old_id.clone(),
11771 new_id: zero,
11772 name: "refs/heads/main".into(),
11773 }],
11774 ..ReceivePackRequest::default()
11775 },
11776 ..ReceivePackPushRequest::default()
11777 };
11778 let features = ReceivePackFeatures {
11779 delete_refs: true,
11780 ..ReceivePackFeatures::default()
11781 };
11782 let installed = std::cell::Cell::new(false);
11783 let deleted = std::cell::RefCell::new(Vec::new());
11784
11785 let report = apply_receive_pack_push_request(
11786 &features,
11787 &request,
11788 |_| Ok(Some(old_id.clone())),
11789 |_| {
11790 installed.set(true);
11791 Ok(())
11792 },
11793 |_| Ok(false),
11794 |_| unreachable!("delete-only request should not apply updates"),
11795 |command| {
11796 deleted.borrow_mut().push(command.name.clone());
11797 Ok(())
11798 },
11799 )
11800 .expect("test operation should succeed");
11801
11802 assert!(!installed.get());
11803 assert_eq!(deleted.into_inner(), vec!["refs/heads/main"]);
11804 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11805 assert!(
11806 apply_receive_pack_push_request(
11807 &features,
11808 &request,
11809 |_| Ok(Some(other_id.clone())),
11810 |_| Ok(()),
11811 |_| Ok(false),
11812 |_| Ok(()),
11813 |_| Ok(()),
11814 )
11815 .is_err()
11816 );
11817 }
11818
11819 #[test]
11820 fn receive_pack_push_request_parses_commands_options_and_packfile() {
11821 let command = ReceivePackCommand {
11822 old_id: ObjectId::from_hex(
11823 ObjectFormat::Sha1,
11824 "1111111111111111111111111111111111111111",
11825 )
11826 .expect("test operation should succeed"),
11827 new_id: ObjectId::from_hex(
11828 ObjectFormat::Sha1,
11829 "2222222222222222222222222222222222222222",
11830 )
11831 .expect("test operation should succeed"),
11832 name: "refs/heads/main".into(),
11833 };
11834 let expected = ReceivePackPushRequest {
11835 commands: ReceivePackRequest {
11836 commands: vec![command],
11837 capabilities: vec![
11838 Capability {
11839 name: "report-status".into(),
11840 value: None,
11841 },
11842 Capability {
11843 name: "push-options".into(),
11844 value: None,
11845 },
11846 ],
11847 ..ReceivePackRequest::default()
11848 },
11849 push_options: Some(vec!["ci.skip".into(), "deploy=staging".into()]),
11850 packfile: b"PACK\x00\x00\x00\x02payload".to_vec(),
11851 };
11852 let encoded =
11853 encode_receive_pack_push_request(&expected).expect("test operation should succeed");
11854
11855 assert_eq!(
11856 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true)
11857 .expect("test operation should succeed"),
11858 expected
11859 );
11860 }
11861
11862 #[test]
11863 fn receive_pack_push_request_preserves_packfile_without_push_options() {
11864 let request = ReceivePackPushRequest {
11865 commands: ReceivePackRequest {
11866 commands: vec![ReceivePackCommand {
11867 old_id: ObjectId::from_hex(
11868 ObjectFormat::Sha1,
11869 "1111111111111111111111111111111111111111",
11870 )
11871 .expect("test operation should succeed"),
11872 new_id: ObjectId::from_hex(
11873 ObjectFormat::Sha1,
11874 "2222222222222222222222222222222222222222",
11875 )
11876 .expect("test operation should succeed"),
11877 name: "refs/heads/main".into(),
11878 }],
11879 ..ReceivePackRequest::default()
11880 },
11881 push_options: None,
11882 packfile: b"0000PACK-like bytes stay raw".to_vec(),
11883 };
11884 let encoded =
11885 encode_receive_pack_push_request(&request).expect("test operation should succeed");
11886
11887 assert_eq!(
11888 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, false)
11889 .expect("test operation should succeed"),
11890 request
11891 );
11892 }
11893
11894 #[test]
11895 fn receive_pack_push_request_streams_round_trip() {
11896 let request = ReceivePackPushRequest {
11897 commands: ReceivePackRequest {
11898 commands: vec![ReceivePackCommand {
11899 old_id: ObjectId::from_hex(
11900 ObjectFormat::Sha1,
11901 "1111111111111111111111111111111111111111",
11902 )
11903 .expect("test operation should succeed"),
11904 new_id: ObjectId::from_hex(
11905 ObjectFormat::Sha1,
11906 "2222222222222222222222222222222222222222",
11907 )
11908 .expect("test operation should succeed"),
11909 name: "refs/heads/main".into(),
11910 }],
11911 capabilities: vec![Capability {
11912 name: "push-options".into(),
11913 value: None,
11914 }],
11915 ..ReceivePackRequest::default()
11916 },
11917 push_options: Some(Vec::new()),
11918 packfile: b"PACKpayload".to_vec(),
11919 };
11920 let mut encoded = Vec::new();
11921 write_receive_pack_push_request(&mut encoded, &request)
11922 .expect("test operation should succeed");
11923
11924 assert_eq!(
11925 read_receive_pack_push_request(ObjectFormat::Sha1, &mut encoded.as_slice(), true)
11926 .expect("test operation should succeed"),
11927 request
11928 );
11929 }
11930
11931 #[test]
11932 fn receive_pack_push_request_rejects_malformed_sections() {
11933 assert!(
11934 parse_receive_pack_push_request(
11935 ObjectFormat::Sha1,
11936 b"0014not-a-command\n0000PACK",
11937 false,
11938 )
11939 .is_err()
11940 );
11941
11942 let request = ReceivePackPushRequest {
11943 commands: ReceivePackRequest {
11944 commands: vec![ReceivePackCommand {
11945 old_id: ObjectId::from_hex(
11946 ObjectFormat::Sha1,
11947 "1111111111111111111111111111111111111111",
11948 )
11949 .expect("test operation should succeed"),
11950 new_id: ObjectId::from_hex(
11951 ObjectFormat::Sha1,
11952 "2222222222222222222222222222222222222222",
11953 )
11954 .expect("test operation should succeed"),
11955 name: "refs/heads/main".into(),
11956 }],
11957 ..ReceivePackRequest::default()
11958 },
11959 push_options: None,
11960 packfile: b"PACKpayload".to_vec(),
11961 };
11962 let encoded =
11963 encode_receive_pack_push_request(&request).expect("test operation should succeed");
11964 assert!(parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true).is_err());
11965
11966 assert!(
11967 encode_receive_pack_push_request(&ReceivePackPushRequest {
11968 commands: ReceivePackRequest {
11969 shallow: vec![
11970 ObjectId::from_hex(
11971 ObjectFormat::Sha1,
11972 "1111111111111111111111111111111111111111",
11973 )
11974 .expect("test operation should succeed")
11975 ],
11976 ..ReceivePackRequest::default()
11977 },
11978 push_options: None,
11979 packfile: Vec::new(),
11980 })
11981 .is_err()
11982 );
11983 }
11984
11985 #[test]
11986 fn receive_pack_report_status_parses_and_encodes_status_lines() {
11987 let frames = vec![
11988 PktLineFrame::Data(b"unpack ok\n".to_vec()),
11989 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
11990 PktLineFrame::Data(b"ng refs/heads/old non-fast-forward\n".to_vec()),
11991 PktLineFrame::Flush,
11992 ];
11993 let report =
11994 parse_receive_pack_report_status(&frames).expect("test operation should succeed");
11995 assert_eq!(
11996 report,
11997 ReceivePackReportStatus {
11998 unpack: ReceivePackUnpackStatus::Ok,
11999 commands: vec![
12000 ReceivePackCommandStatus::Ok {
12001 name: "refs/heads/main".into(),
12002 },
12003 ReceivePackCommandStatus::Ng {
12004 name: "refs/heads/old".into(),
12005 message: "non-fast-forward".into(),
12006 },
12007 ],
12008 }
12009 );
12010 assert_eq!(
12011 encode_receive_pack_report_status(&report).expect("test operation should succeed"),
12012 frames
12013 );
12014
12015 let frames = vec![
12016 PktLineFrame::Data(b"unpack pack exceeds maximum size\n".to_vec()),
12017 PktLineFrame::Flush,
12018 ];
12019 assert_eq!(
12020 parse_receive_pack_report_status(&frames).expect("test operation should succeed"),
12021 ReceivePackReportStatus {
12022 unpack: ReceivePackUnpackStatus::Error("pack exceeds maximum size".into()),
12023 commands: Vec::new(),
12024 }
12025 );
12026 }
12027
12028 #[test]
12029 fn receive_pack_report_status_streams_round_trip() {
12030 let report = ReceivePackReportStatus {
12031 unpack: ReceivePackUnpackStatus::Ok,
12032 commands: vec![ReceivePackCommandStatus::Ok {
12033 name: "refs/heads/main".into(),
12034 }],
12035 };
12036 let mut encoded = Vec::new();
12037 write_receive_pack_report_status(&mut encoded, &report)
12038 .expect("test operation should succeed");
12039 encoded.extend_from_slice(b"tail");
12040
12041 let mut input = encoded.as_slice();
12042 assert_eq!(
12043 read_receive_pack_report_status(&mut input).expect("test operation should succeed"),
12044 report
12045 );
12046 assert_eq!(input, b"tail");
12047 }
12048
12049 #[test]
12050 fn receive_pack_report_status_rejects_malformed_status_lines() {
12051 assert!(parse_receive_pack_report_status(&[]).is_err());
12052 assert!(
12053 parse_receive_pack_report_status(&[
12054 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12055 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12056 ])
12057 .is_err()
12058 );
12059 assert!(
12060 parse_receive_pack_report_status(&[
12061 PktLineFrame::Flush,
12062 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12063 ])
12064 .is_err()
12065 );
12066 assert!(
12067 parse_receive_pack_report_status(&[
12068 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12069 PktLineFrame::Data(b"bad refs/heads/main\n".to_vec()),
12070 PktLineFrame::Flush,
12071 ])
12072 .is_err()
12073 );
12074 assert!(
12075 parse_receive_pack_report_status(&[
12076 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12077 PktLineFrame::Data(b"ng refs/heads/main\n".to_vec()),
12078 PktLineFrame::Flush,
12079 ])
12080 .is_err()
12081 );
12082 assert!(
12083 encode_receive_pack_report_status(&ReceivePackReportStatus {
12084 unpack: ReceivePackUnpackStatus::Error("".into()),
12085 commands: Vec::new(),
12086 })
12087 .is_err()
12088 );
12089 assert!(
12090 encode_receive_pack_report_status(&ReceivePackReportStatus {
12091 unpack: ReceivePackUnpackStatus::Ok,
12092 commands: vec![ReceivePackCommandStatus::Ok {
12093 name: "bad ref".into(),
12094 }],
12095 })
12096 .is_err()
12097 );
12098 }
12099
12100 #[test]
12101 fn receive_pack_report_status_v2_parses_and_encodes_options() {
12102 let old_oid = ObjectId::from_hex(
12103 ObjectFormat::Sha1,
12104 "1111111111111111111111111111111111111111",
12105 )
12106 .expect("test operation should succeed");
12107 let new_oid = ObjectId::from_hex(
12108 ObjectFormat::Sha1,
12109 "2222222222222222222222222222222222222222",
12110 )
12111 .expect("test operation should succeed");
12112 let frames = vec![
12113 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12114 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12115 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12116 PktLineFrame::Data(
12117 b"option old-oid 1111111111111111111111111111111111111111\n".to_vec(),
12118 ),
12119 PktLineFrame::Data(
12120 b"option new-oid 2222222222222222222222222222222222222222\n".to_vec(),
12121 ),
12122 PktLineFrame::Data(b"option forced-update\n".to_vec()),
12123 PktLineFrame::Data(b"ng refs/heads/old rejected by hook\n".to_vec()),
12124 PktLineFrame::Flush,
12125 ];
12126 let report = parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &frames)
12127 .expect("test operation should succeed");
12128 assert_eq!(
12129 report,
12130 ReceivePackReportStatusV2 {
12131 unpack: ReceivePackUnpackStatus::Ok,
12132 commands: vec![
12133 ReceivePackCommandStatusV2::Ok {
12134 name: "refs/for/main".into(),
12135 options: ReceivePackCommandStatusV2Options {
12136 refname: Some("refs/heads/main".into()),
12137 old_oid: Some(old_oid),
12138 new_oid: Some(new_oid),
12139 forced_update: true,
12140 },
12141 },
12142 ReceivePackCommandStatusV2::Ng {
12143 name: "refs/heads/old".into(),
12144 message: "rejected by hook".into(),
12145 },
12146 ],
12147 }
12148 );
12149 assert_eq!(
12150 encode_receive_pack_report_status_v2(&report).expect("test operation should succeed"),
12151 frames
12152 );
12153 }
12154
12155 #[test]
12156 fn receive_pack_report_status_v2_streams_round_trip() {
12157 let report = ReceivePackReportStatusV2 {
12158 unpack: ReceivePackUnpackStatus::Ok,
12159 commands: vec![ReceivePackCommandStatusV2::Ok {
12160 name: "refs/for/main".into(),
12161 options: ReceivePackCommandStatusV2Options {
12162 refname: Some("refs/heads/main".into()),
12163 old_oid: None,
12164 new_oid: None,
12165 forced_update: false,
12166 },
12167 }],
12168 };
12169 let mut encoded = Vec::new();
12170 write_receive_pack_report_status_v2(&mut encoded, &report)
12171 .expect("test operation should succeed");
12172 encoded.extend_from_slice(b"tail");
12173
12174 let mut input = encoded.as_slice();
12175 assert_eq!(
12176 read_receive_pack_report_status_v2(ObjectFormat::Sha1, &mut input)
12177 .expect("test operation should succeed"),
12178 report
12179 );
12180 assert_eq!(input, b"tail");
12181 }
12182
12183 #[test]
12184 fn receive_pack_report_status_v2_rejects_malformed_options() {
12185 assert!(parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &[]).is_err());
12186 assert!(
12187 parse_receive_pack_report_status_v2(
12188 ObjectFormat::Sha1,
12189 &[
12190 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12191 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12192 PktLineFrame::Flush,
12193 ],
12194 )
12195 .is_err()
12196 );
12197 assert!(
12198 parse_receive_pack_report_status_v2(
12199 ObjectFormat::Sha1,
12200 &[
12201 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12202 PktLineFrame::Data(b"ng refs/heads/main rejected\n".to_vec()),
12203 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12204 PktLineFrame::Flush,
12205 ],
12206 )
12207 .is_err()
12208 );
12209 assert!(
12210 parse_receive_pack_report_status_v2(
12211 ObjectFormat::Sha1,
12212 &[
12213 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12214 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12215 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12216 PktLineFrame::Data(b"option refname refs/heads/next\n".to_vec()),
12217 PktLineFrame::Flush,
12218 ],
12219 )
12220 .is_err()
12221 );
12222 assert!(
12223 parse_receive_pack_report_status_v2(
12224 ObjectFormat::Sha1,
12225 &[
12226 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12227 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12228 PktLineFrame::Data(b"option old-oid not-an-oid\n".to_vec()),
12229 PktLineFrame::Flush,
12230 ],
12231 )
12232 .is_err()
12233 );
12234 assert!(
12235 encode_receive_pack_report_status_v2(&ReceivePackReportStatusV2 {
12236 unpack: ReceivePackUnpackStatus::Ok,
12237 commands: vec![ReceivePackCommandStatusV2::Ok {
12238 name: "refs/for/main".into(),
12239 options: ReceivePackCommandStatusV2Options {
12240 refname: Some("bad ref".into()),
12241 ..ReceivePackCommandStatusV2Options::default()
12242 },
12243 }],
12244 })
12245 .is_err()
12246 );
12247 }
12248
12249 #[test]
12250 fn receive_pack_push_options_parse_and_encode_options() {
12251 let frames = vec![
12252 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12253 PktLineFrame::Data(b"deploy target=staging\n".to_vec()),
12254 PktLineFrame::Data(b"\n".to_vec()),
12255 PktLineFrame::Flush,
12256 ];
12257 let options =
12258 parse_receive_pack_push_options(&frames).expect("test operation should succeed");
12259 assert_eq!(
12260 options,
12261 vec![
12262 "ci.skip".to_string(),
12263 "deploy target=staging".to_string(),
12264 String::new(),
12265 ]
12266 );
12267 assert_eq!(
12268 encode_receive_pack_push_options(&options).expect("test operation should succeed"),
12269 frames
12270 );
12271 assert_eq!(
12272 parse_receive_pack_push_options(&[PktLineFrame::Flush])
12273 .expect("test operation should succeed"),
12274 Vec::<String>::new()
12275 );
12276 }
12277
12278 #[test]
12279 fn receive_pack_push_options_streams_round_trip() {
12280 let options = vec!["ci.skip".to_string(), "reviewer=alice".to_string()];
12281 let mut encoded = Vec::new();
12282 write_receive_pack_push_options(&mut encoded, &options)
12283 .expect("test operation should succeed");
12284 encoded.extend_from_slice(b"PACK");
12285
12286 let mut input = encoded.as_slice();
12287 assert_eq!(
12288 read_receive_pack_push_options(&mut input).expect("test operation should succeed"),
12289 options
12290 );
12291 assert_eq!(input, b"PACK");
12292 }
12293
12294 #[test]
12295 fn receive_pack_push_options_reject_malformed_streams() {
12296 assert!(
12297 parse_receive_pack_push_options(&[PktLineFrame::Data(b"ci.skip\n".to_vec())]).is_err()
12298 );
12299 assert!(
12300 parse_receive_pack_push_options(&[PktLineFrame::Delimiter, PktLineFrame::Flush])
12301 .is_err()
12302 );
12303 assert!(
12304 parse_receive_pack_push_options(&[
12305 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12306 PktLineFrame::Flush,
12307 PktLineFrame::Data(b"after\n".to_vec()),
12308 ])
12309 .is_err()
12310 );
12311 assert!(
12312 parse_receive_pack_push_options(&[
12313 PktLineFrame::Data(b"bad\0option\n".to_vec()),
12314 PktLineFrame::Flush,
12315 ])
12316 .is_err()
12317 );
12318 assert!(encode_receive_pack_push_options(&["bad\noption".to_string()]).is_err());
12319 }
12320
12321 #[test]
12322 fn protocol_v2_advertisement_parses_version_and_capabilities() {
12323 let frames = parse_pkt_line_stream(
12324 b"000eversion 2\n0015agent=git/2.54.0\n0013ls-refs=unborn\n0027fetch=shallow wait-for-done filter\n0012server-option\n0000",
12325 )
12326 .expect("test operation should succeed");
12327 let handshake =
12328 parse_protocol_v2_advertisement(&frames).expect("test operation should succeed");
12329 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12330 assert_eq!(
12331 handshake.capabilities,
12332 vec![
12333 Capability {
12334 name: "agent".into(),
12335 value: Some("git/2.54.0".into()),
12336 },
12337 Capability {
12338 name: "ls-refs".into(),
12339 value: Some("unborn".into()),
12340 },
12341 Capability {
12342 name: "fetch".into(),
12343 value: Some("shallow wait-for-done filter".into()),
12344 },
12345 Capability {
12346 name: "server-option".into(),
12347 value: None,
12348 },
12349 ]
12350 );
12351 assert_eq!(
12352 encode_protocol_v2_advertisement(&handshake).expect("test operation should succeed"),
12353 frames
12354 );
12355 }
12356
12357 #[test]
12358 fn protocol_v2_advertisement_reads_until_flush() {
12359 let mut input = b"000eversion 2\n0013ls-refs=unborn\n0000next-session".as_slice();
12360 let handshake =
12361 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed");
12362 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12363 assert_eq!(
12364 handshake.capabilities,
12365 vec![Capability {
12366 name: "ls-refs".into(),
12367 value: Some("unborn".into()),
12368 }]
12369 );
12370 assert_eq!(input, b"next-session");
12371 }
12372
12373 #[test]
12374 fn protocol_v2_advertisement_writes_stream() {
12375 let handshake = TransportHandshake {
12376 protocol: ProtocolVersion::V2,
12377 capabilities: vec![
12378 Capability {
12379 name: "agent".into(),
12380 value: Some("sley/0".into()),
12381 },
12382 Capability {
12383 name: "fetch".into(),
12384 value: Some("shallow filter".into()),
12385 },
12386 ],
12387 };
12388 let mut encoded = Vec::new();
12389 write_protocol_v2_advertisement(&mut encoded, &handshake)
12390 .expect("test operation should succeed");
12391 let mut input = encoded.as_slice();
12392 assert_eq!(
12393 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed"),
12394 handshake
12395 );
12396 assert!(input.is_empty());
12397 assert!(
12398 encode_protocol_v2_advertisement(&TransportHandshake {
12399 protocol: ProtocolVersion::V1,
12400 capabilities: Vec::new(),
12401 })
12402 .is_err()
12403 );
12404 }
12405
12406 #[test]
12407 fn protocol_v2_advertisement_rejects_malformed_sequences() {
12408 assert!(parse_protocol_v2_advertisement(&[]).is_err());
12409 assert!(
12410 parse_protocol_v2_advertisement(&[
12411 PktLineFrame::Data(b"version 1\n".to_vec()),
12412 PktLineFrame::Flush,
12413 ])
12414 .is_err()
12415 );
12416 assert!(
12417 parse_protocol_v2_advertisement(&[PktLineFrame::Data(b"version 2\n".to_vec())])
12418 .is_err()
12419 );
12420 assert!(
12421 parse_protocol_v2_advertisement(&[
12422 PktLineFrame::Data(b"version 2\n".to_vec()),
12423 PktLineFrame::Delimiter,
12424 ])
12425 .is_err()
12426 );
12427 assert!(
12428 parse_protocol_v2_advertisement(&[
12429 PktLineFrame::Data(b"version 2\n".to_vec()),
12430 PktLineFrame::Data(b"fetch=\n".to_vec()),
12431 PktLineFrame::Flush,
12432 ])
12433 .is_err()
12434 );
12435 }
12436
12437 #[test]
12438 fn protocol_v2_command_request_parses_and_encodes_sections() {
12439 let frames = parse_pkt_line_stream(
12440 b"0014command=ls-refs\n0011agent=sley/0\n0017object-format=sha1\n00010009peel\n000csymrefs\n001bref-prefix refs/heads/\n0000",
12441 )
12442 .expect("test operation should succeed");
12443 let request =
12444 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12445 assert_eq!(
12446 request,
12447 ProtocolV2CommandRequest {
12448 command: "ls-refs".into(),
12449 capabilities: vec![
12450 Capability {
12451 name: "agent".into(),
12452 value: Some("sley/0".into()),
12453 },
12454 Capability {
12455 name: "object-format".into(),
12456 value: Some("sha1".into()),
12457 },
12458 ],
12459 arguments: vec![
12460 b"peel".to_vec(),
12461 b"symrefs".to_vec(),
12462 b"ref-prefix refs/heads/".to_vec(),
12463 ],
12464 }
12465 );
12466 assert_eq!(
12467 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12468 frames
12469 );
12470 }
12471
12472 #[test]
12473 fn protocol_v2_command_request_allows_no_argument_section() {
12474 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12475 .expect("test operation should succeed");
12476 let request =
12477 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12478 assert_eq!(
12479 request,
12480 ProtocolV2CommandRequest {
12481 command: "fetch".into(),
12482 capabilities: Vec::new(),
12483 arguments: Vec::new(),
12484 }
12485 );
12486 assert_eq!(
12487 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12488 frames
12489 );
12490 }
12491
12492 #[test]
12493 fn protocol_v2_request_parses_commands_and_empty_done() {
12494 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12495 .expect("test operation should succeed");
12496 let command = ProtocolV2CommandRequest {
12497 command: "fetch".into(),
12498 capabilities: Vec::new(),
12499 arguments: Vec::new(),
12500 };
12501 assert_eq!(
12502 parse_protocol_v2_request(&frames).expect("test operation should succeed"),
12503 ProtocolV2Request::Command(command.clone())
12504 );
12505 assert_eq!(
12506 encode_protocol_v2_request(&ProtocolV2Request::Command(command))
12507 .expect("test operation should succeed"),
12508 frames
12509 );
12510
12511 assert_eq!(
12512 parse_protocol_v2_request(&[PktLineFrame::Flush])
12513 .expect("test operation should succeed"),
12514 ProtocolV2Request::Done
12515 );
12516 assert_eq!(
12517 encode_protocol_v2_request(&ProtocolV2Request::Done)
12518 .expect("test operation should succeed"),
12519 vec![PktLineFrame::Flush]
12520 );
12521 }
12522
12523 #[test]
12524 fn protocol_v2_request_streams_empty_done() {
12525 let mut encoded = Vec::new();
12526 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
12527 .expect("test operation should succeed");
12528 encoded.extend_from_slice(b"tail");
12529
12530 let mut input = encoded.as_slice();
12531 assert_eq!(
12532 read_protocol_v2_request(&mut input).expect("test operation should succeed"),
12533 ProtocolV2Request::Done
12534 );
12535 assert_eq!(input, b"tail");
12536 let mut command_input = encoded.as_slice();
12537 assert!(read_protocol_v2_command_request(&mut command_input).is_err());
12538 }
12539
12540 #[test]
12541 fn protocol_v2_command_request_streams_round_trip() {
12542 let request = ProtocolV2CommandRequest {
12543 command: "ls-refs".into(),
12544 capabilities: vec![Capability {
12545 name: "agent".into(),
12546 value: Some("sley/0".into()),
12547 }],
12548 arguments: vec![b"peel".to_vec(), b"symrefs".to_vec()],
12549 };
12550 let mut encoded = Vec::new();
12551 write_protocol_v2_command_request(&mut encoded, &request)
12552 .expect("test operation should succeed");
12553 encoded.extend_from_slice(b"tail");
12554
12555 let mut input = encoded.as_slice();
12556 assert_eq!(
12557 read_protocol_v2_command_request(&mut input).expect("test operation should succeed"),
12558 request
12559 );
12560 assert_eq!(input, b"tail");
12561 }
12562
12563 #[test]
12564 fn protocol_v2_command_request_rejects_malformed_sequences() {
12565 assert!(parse_protocol_v2_command_request(&[]).is_err());
12566 assert!(
12567 parse_protocol_v2_command_request(&[
12568 PktLineFrame::Data(b"agent=sley/0\n".to_vec()),
12569 PktLineFrame::Flush,
12570 ])
12571 .is_err()
12572 );
12573 assert!(
12574 parse_protocol_v2_command_request(&[
12575 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12576 PktLineFrame::Delimiter,
12577 PktLineFrame::Delimiter,
12578 PktLineFrame::Flush,
12579 ])
12580 .is_err()
12581 );
12582 assert!(
12583 parse_protocol_v2_command_request(&[
12584 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12585 PktLineFrame::Delimiter,
12586 PktLineFrame::Data(b"\n".to_vec()),
12587 PktLineFrame::Flush,
12588 ])
12589 .is_err()
12590 );
12591 assert!(
12592 encode_protocol_v2_command_request(&ProtocolV2CommandRequest {
12593 command: "bad command".into(),
12594 capabilities: Vec::new(),
12595 arguments: Vec::new(),
12596 })
12597 .is_err()
12598 );
12599 }
12600
12601 #[test]
12602 fn protocol_v2_ls_refs_request_parses_and_encodes_arguments() {
12603 let command = ProtocolV2CommandRequest {
12604 command: "ls-refs".into(),
12605 capabilities: Vec::new(),
12606 arguments: vec![
12607 b"peel".to_vec(),
12608 b"symrefs".to_vec(),
12609 b"unborn".to_vec(),
12610 b"ref-prefix HEAD".to_vec(),
12611 b"ref-prefix refs/heads/".to_vec(),
12612 ],
12613 };
12614 let request = ProtocolV2LsRefsRequest::from_command_request(&command)
12615 .expect("test operation should succeed");
12616 assert_eq!(
12617 request,
12618 ProtocolV2LsRefsRequest {
12619 peel: true,
12620 symrefs: true,
12621 unborn: true,
12622 ref_prefixes: vec!["HEAD".into(), "refs/heads/".into()],
12623 }
12624 );
12625 assert_eq!(
12626 request
12627 .to_command_request()
12628 .expect("test operation should succeed"),
12629 command
12630 );
12631 assert!(
12632 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12633 command: "fetch".into(),
12634 capabilities: Vec::new(),
12635 arguments: Vec::new(),
12636 })
12637 .is_err()
12638 );
12639 assert!(
12640 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12641 command: "ls-refs".into(),
12642 capabilities: Vec::new(),
12643 arguments: vec![b"ref-prefix ".to_vec()],
12644 })
12645 .is_err()
12646 );
12647 }
12648
12649 #[test]
12650 fn protocol_v2_ls_refs_request_streams_round_trip() {
12651 let request = ProtocolV2LsRefsRequest {
12652 peel: true,
12653 symrefs: true,
12654 unborn: false,
12655 ref_prefixes: vec!["HEAD".into(), "refs/tags/".into()],
12656 };
12657 let mut encoded = Vec::new();
12658 write_protocol_v2_ls_refs_request(&mut encoded, &request)
12659 .expect("test operation should succeed");
12660 encoded.extend_from_slice(b"tail");
12661
12662 let mut input = encoded.as_slice();
12663 assert_eq!(
12664 read_protocol_v2_ls_refs_request(&mut input).expect("test operation should succeed"),
12665 request
12666 );
12667 assert_eq!(input, b"tail");
12668 }
12669
12670 #[test]
12671 fn protocol_v2_ls_refs_response_parses_and_encodes_records() {
12672 let oid = ObjectId::from_hex(
12673 ObjectFormat::Sha1,
12674 "1111111111111111111111111111111111111111",
12675 )
12676 .expect("test operation should succeed");
12677 let peeled = ObjectId::from_hex(
12678 ObjectFormat::Sha1,
12679 "2222222222222222222222222222222222222222",
12680 )
12681 .expect("test operation should succeed");
12682 let frames = vec![
12683 PktLineFrame::Data(
12684 b"1111111111111111111111111111111111111111 refs/tags/v1 peeled:2222222222222222222222222222222222222222 symref-target:refs/heads/main custom\n"
12685 .to_vec(),
12686 ),
12687 PktLineFrame::Data(b"unborn HEAD symref-target:refs/heads/main\n".to_vec()),
12688 PktLineFrame::Flush,
12689 ];
12690 let records = parse_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &frames)
12691 .expect("test operation should succeed");
12692 assert_eq!(
12693 records,
12694 vec![
12695 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12696 oid,
12697 name: "refs/tags/v1".into(),
12698 peeled: Some(peeled),
12699 symref_target: Some("refs/heads/main".into()),
12700 attributes: vec!["custom".into()],
12701 }),
12702 ProtocolV2LsRefsRecord::Unborn {
12703 name: "HEAD".into(),
12704 symref_target: Some("refs/heads/main".into()),
12705 attributes: Vec::new(),
12706 },
12707 ]
12708 );
12709 assert_eq!(
12710 encode_protocol_v2_ls_refs_response(&records).expect("test operation should succeed"),
12711 frames
12712 );
12713 }
12714
12715 #[test]
12716 fn protocol_v2_ls_refs_response_streams_round_trip() {
12717 let oid = ObjectId::from_hex(
12718 ObjectFormat::Sha1,
12719 "1111111111111111111111111111111111111111",
12720 )
12721 .expect("test operation should succeed");
12722 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12723 oid,
12724 name: "refs/heads/main".into(),
12725 peeled: None,
12726 symref_target: Some("refs/heads/trunk".into()),
12727 attributes: vec!["custom".into()],
12728 })];
12729 let mut encoded = Vec::new();
12730 write_protocol_v2_ls_refs_response(&mut encoded, &records)
12731 .expect("test operation should succeed");
12732 encoded.extend_from_slice(b"tail");
12733
12734 let mut input = encoded.as_slice();
12735 assert_eq!(
12736 read_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &mut input)
12737 .expect("test operation should succeed"),
12738 records
12739 );
12740 assert_eq!(input, b"tail");
12741 }
12742
12743 #[test]
12744 fn protocol_v2_ls_refs_response_reads_stateless_response_end() {
12745 let oid = ObjectId::from_hex(
12746 ObjectFormat::Sha1,
12747 "1111111111111111111111111111111111111111",
12748 )
12749 .expect("test operation should succeed");
12750 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12751 oid,
12752 name: "refs/heads/main".into(),
12753 peeled: None,
12754 symref_target: None,
12755 attributes: Vec::new(),
12756 })];
12757 let mut encoded = Vec::new();
12758 write_protocol_v2_ls_refs_response_with_response_end(&mut encoded, &records)
12759 .expect("test operation should succeed");
12760 encoded.extend_from_slice(b"tail");
12761
12762 let mut input = encoded.as_slice();
12763 assert_eq!(
12764 read_protocol_v2_ls_refs_response_until_response_end(ObjectFormat::Sha1, &mut input)
12765 .expect("test operation should succeed"),
12766 records
12767 );
12768 assert_eq!(input, b"tail");
12769 assert!(
12770 parse_protocol_v2_ls_refs_response(
12771 ObjectFormat::Sha1,
12772 &[
12773 PktLineFrame::Data(
12774 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
12775 ),
12776 PktLineFrame::ResponseEnd
12777 ],
12778 )
12779 .is_err()
12780 );
12781 }
12782
12783 #[test]
12784 fn protocol_v2_ls_refs_exchange_writes_request_and_reads_response() {
12785 let oid = ObjectId::from_hex(
12786 ObjectFormat::Sha1,
12787 "1111111111111111111111111111111111111111",
12788 )
12789 .expect("test operation should succeed");
12790 let request = ProtocolV2LsRefsRequest {
12791 peel: true,
12792 symrefs: true,
12793 unborn: false,
12794 ref_prefixes: vec!["refs/heads/".into()],
12795 };
12796 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12797 oid,
12798 name: "refs/heads/main".into(),
12799 peeled: None,
12800 symref_target: None,
12801 attributes: Vec::new(),
12802 })];
12803 let mut response = Vec::new();
12804 write_protocol_v2_ls_refs_response(&mut response, &records)
12805 .expect("test operation should succeed");
12806
12807 let mut input = response.as_slice();
12808 let mut output = Vec::new();
12809 assert_eq!(
12810 exchange_protocol_v2_ls_refs(ObjectFormat::Sha1, &mut input, &mut output, &request)
12811 .expect("test operation should succeed"),
12812 records
12813 );
12814 assert!(input.is_empty());
12815 let mut output_read = output.as_slice();
12816 assert_eq!(
12817 read_protocol_v2_ls_refs_request(&mut output_read)
12818 .expect("test operation should succeed"),
12819 request
12820 );
12821 }
12822
12823 #[test]
12824 fn protocol_v2_ls_refs_response_rejects_malformed_records() {
12825 assert!(
12826 parse_protocol_v2_ls_refs_response(
12827 ObjectFormat::Sha1,
12828 &[PktLineFrame::Data(
12829 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
12830 )],
12831 )
12832 .is_err()
12833 );
12834 assert!(
12835 parse_protocol_v2_ls_refs_response(
12836 ObjectFormat::Sha1,
12837 &[
12838 PktLineFrame::Data(
12839 b"1111111111111111111111111111111111111111 refs/heads/main peeled:2222222222222222222222222222222222222222 peeled:3333333333333333333333333333333333333333\n"
12840 .to_vec()
12841 ),
12842 PktLineFrame::Flush,
12843 ],
12844 )
12845 .is_err()
12846 );
12847 assert!(
12848 parse_protocol_v2_ls_refs_response(
12849 ObjectFormat::Sha1,
12850 &[
12851 PktLineFrame::Data(
12852 b"unborn HEAD peeled:2222222222222222222222222222222222222222\n".to_vec()
12853 ),
12854 PktLineFrame::Flush,
12855 ],
12856 )
12857 .is_err()
12858 );
12859 assert!(
12860 encode_protocol_v2_ls_refs_response(&[ProtocolV2LsRefsRecord::Ref(
12861 ProtocolV2LsRefsRef {
12862 oid: ObjectId::from_hex(
12863 ObjectFormat::Sha1,
12864 "1111111111111111111111111111111111111111",
12865 )
12866 .expect("test operation should succeed"),
12867 name: "refs/heads/main".into(),
12868 peeled: None,
12869 symref_target: None,
12870 attributes: vec!["peeled:2222222222222222222222222222222222222222".into()],
12871 }
12872 )])
12873 .is_err()
12874 );
12875 }
12876
12877 #[test]
12878 fn protocol_v2_fetch_request_parses_and_encodes_arguments() {
12879 let want = ObjectId::from_hex(
12880 ObjectFormat::Sha1,
12881 "1111111111111111111111111111111111111111",
12882 )
12883 .expect("test operation should succeed");
12884 let have = ObjectId::from_hex(
12885 ObjectFormat::Sha1,
12886 "2222222222222222222222222222222222222222",
12887 )
12888 .expect("test operation should succeed");
12889 let shallow = ObjectId::from_hex(
12890 ObjectFormat::Sha1,
12891 "3333333333333333333333333333333333333333",
12892 )
12893 .expect("test operation should succeed");
12894 let command = ProtocolV2CommandRequest {
12895 command: "fetch".into(),
12896 capabilities: Vec::new(),
12897 arguments: vec![
12898 b"want 1111111111111111111111111111111111111111".to_vec(),
12899 b"want-ref refs/heads/main".to_vec(),
12900 b"have 2222222222222222222222222222222222222222".to_vec(),
12901 b"shallow 3333333333333333333333333333333333333333".to_vec(),
12902 b"deepen 10".to_vec(),
12903 b"deepen-since 123456789".to_vec(),
12904 b"deepen-not refs/tags/v1".to_vec(),
12905 b"deepen-relative".to_vec(),
12906 b"filter blob:none".to_vec(),
12907 b"packfile-uris http,https".to_vec(),
12908 b"thin-pack".to_vec(),
12909 b"no-progress".to_vec(),
12910 b"include-tag".to_vec(),
12911 b"ofs-delta".to_vec(),
12912 b"sideband-all".to_vec(),
12913 b"wait-for-done".to_vec(),
12914 b"done".to_vec(),
12915 ],
12916 };
12917 let request = ProtocolV2FetchRequest::from_command_request(ObjectFormat::Sha1, &command)
12918 .expect("test operation should succeed");
12919 assert_eq!(
12920 request,
12921 ProtocolV2FetchRequest {
12922 wants: vec![want],
12923 want_refs: vec!["refs/heads/main".into()],
12924 haves: vec![have],
12925 shallow: vec![shallow],
12926 deepen: Some(10),
12927 deepen_since: Some(123456789),
12928 deepen_not: vec!["refs/tags/v1".into()],
12929 deepen_relative: true,
12930 filter: Some("blob:none".into()),
12931 packfile_uris: Some("http,https".into()),
12932 thin_pack: true,
12933 no_progress: true,
12934 include_tag: true,
12935 ofs_delta: true,
12936 sideband_all: true,
12937 wait_for_done: true,
12938 done: true,
12939 }
12940 );
12941 assert_eq!(
12942 request
12943 .to_command_request()
12944 .expect("test operation should succeed"),
12945 command
12946 );
12947 }
12948
12949 #[test]
12950 fn protocol_v2_fetch_request_rejects_malformed_arguments() {
12951 assert!(
12952 ProtocolV2FetchRequest::from_command_request(
12953 ObjectFormat::Sha1,
12954 &ProtocolV2CommandRequest {
12955 command: "ls-refs".into(),
12956 capabilities: Vec::new(),
12957 arguments: Vec::new(),
12958 },
12959 )
12960 .is_err()
12961 );
12962 assert!(
12963 ProtocolV2FetchRequest::from_command_request(
12964 ObjectFormat::Sha1,
12965 &ProtocolV2CommandRequest {
12966 command: "fetch".into(),
12967 capabilities: Vec::new(),
12968 arguments: vec![b"want not-an-oid".to_vec()],
12969 },
12970 )
12971 .is_err()
12972 );
12973 assert!(
12974 ProtocolV2FetchRequest::from_command_request(
12975 ObjectFormat::Sha1,
12976 &ProtocolV2CommandRequest {
12977 command: "fetch".into(),
12978 capabilities: Vec::new(),
12979 arguments: vec![b"deepen 0".to_vec()],
12980 },
12981 )
12982 .is_err()
12983 );
12984 assert!(
12985 ProtocolV2FetchRequest::from_command_request(
12986 ObjectFormat::Sha1,
12987 &ProtocolV2CommandRequest {
12988 command: "fetch".into(),
12989 capabilities: Vec::new(),
12990 arguments: vec![b"filter blob:none".to_vec(), b"filter tree:0".to_vec()],
12991 },
12992 )
12993 .is_err()
12994 );
12995 assert!(
12996 ProtocolV2FetchRequest {
12997 deepen: Some(0),
12998 ..ProtocolV2FetchRequest::default()
12999 }
13000 .to_command_request()
13001 .is_err()
13002 );
13003 }
13004
13005 #[test]
13006 fn protocol_v2_fetch_request_streams_round_trip() {
13007 let want = ObjectId::from_hex(
13008 ObjectFormat::Sha1,
13009 "1111111111111111111111111111111111111111",
13010 )
13011 .expect("test operation should succeed");
13012 let have = ObjectId::from_hex(
13013 ObjectFormat::Sha1,
13014 "2222222222222222222222222222222222222222",
13015 )
13016 .expect("test operation should succeed");
13017 let request = ProtocolV2FetchRequest {
13018 wants: vec![want],
13019 haves: vec![have],
13020 deepen: Some(5),
13021 filter: Some("blob:none".into()),
13022 thin_pack: true,
13023 done: true,
13024 ..ProtocolV2FetchRequest::default()
13025 };
13026 let mut encoded = Vec::new();
13027 write_protocol_v2_fetch_request(&mut encoded, &request)
13028 .expect("test operation should succeed");
13029 encoded.extend_from_slice(b"tail");
13030
13031 let mut input = encoded.as_slice();
13032 assert_eq!(
13033 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut input)
13034 .expect("test operation should succeed"),
13035 request
13036 );
13037 assert_eq!(input, b"tail");
13038 }
13039
13040 #[test]
13041 fn protocol_v2_fetch_response_parses_and_encodes_sections() {
13042 let ack = ObjectId::from_hex(
13043 ObjectFormat::Sha1,
13044 "1111111111111111111111111111111111111111",
13045 )
13046 .expect("test operation should succeed");
13047 let shallow = ObjectId::from_hex(
13048 ObjectFormat::Sha1,
13049 "2222222222222222222222222222222222222222",
13050 )
13051 .expect("test operation should succeed");
13052 let wanted = ObjectId::from_hex(
13053 ObjectFormat::Sha1,
13054 "3333333333333333333333333333333333333333",
13055 )
13056 .expect("test operation should succeed");
13057 let pack_hash = ObjectId::from_hex(
13058 ObjectFormat::Sha1,
13059 "4444444444444444444444444444444444444444",
13060 )
13061 .expect("test operation should succeed");
13062 let frames = vec![
13063 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13064 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111\n".to_vec()),
13065 PktLineFrame::Data(b"ready\n".to_vec()),
13066 PktLineFrame::Delimiter,
13067 PktLineFrame::Data(b"shallow-info\n".to_vec()),
13068 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
13069 PktLineFrame::Delimiter,
13070 PktLineFrame::Data(b"wanted-refs\n".to_vec()),
13071 PktLineFrame::Data(
13072 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
13073 ),
13074 PktLineFrame::Delimiter,
13075 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13076 PktLineFrame::Data(
13077 b"4444444444444444444444444444444444444444 https://example.invalid/pack-a.pack\n"
13078 .to_vec(),
13079 ),
13080 PktLineFrame::Delimiter,
13081 PktLineFrame::Data(b"packfile\n".to_vec()),
13082 PktLineFrame::Data(b"\x01PACK bytes".to_vec()),
13083 PktLineFrame::Flush,
13084 ];
13085 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13086 .expect("test operation should succeed");
13087 assert_eq!(
13088 sections,
13089 vec![
13090 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13091 ProtocolV2FetchAcknowledgment::Ack(ack),
13092 ProtocolV2FetchAcknowledgment::Ready,
13093 ]),
13094 ProtocolV2FetchResponseSection::ShallowInfo(vec![
13095 ProtocolV2FetchShallowInfo::Shallow(shallow)
13096 ]),
13097 ProtocolV2FetchResponseSection::WantedRefs(vec![ProtocolV2FetchWantedRef {
13098 oid: wanted,
13099 name: "refs/heads/main".into(),
13100 }]),
13101 ProtocolV2FetchResponseSection::PackfileUris(vec![ProtocolV2FetchPackfileUri {
13102 pack_hash,
13103 uri: "https://example.invalid/pack-a.pack".into(),
13104 }]),
13105 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13106 ]
13107 );
13108 assert_eq!(
13109 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13110 frames
13111 );
13112 }
13113
13114 #[test]
13115 fn protocol_v2_fetch_response_preserves_unknown_sections() {
13116 let frames = vec![
13117 PktLineFrame::Data(b"server-feature\n".to_vec()),
13118 PktLineFrame::Data(b"opaque line\n".to_vec()),
13119 PktLineFrame::Flush,
13120 ];
13121 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13122 .expect("test operation should succeed");
13123 assert_eq!(
13124 sections,
13125 vec![ProtocolV2FetchResponseSection::Unknown {
13126 name: "server-feature".into(),
13127 lines: vec![b"opaque line\n".to_vec()],
13128 }]
13129 );
13130 assert_eq!(
13131 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13132 frames
13133 );
13134 }
13135
13136 #[test]
13137 fn protocol_v2_fetch_response_streams_round_trip() {
13138 let ack = ObjectId::from_hex(
13139 ObjectFormat::Sha1,
13140 "1111111111111111111111111111111111111111",
13141 )
13142 .expect("test operation should succeed");
13143 let sections = vec![
13144 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13145 ProtocolV2FetchAcknowledgment::Ack(ack),
13146 ProtocolV2FetchAcknowledgment::Ready,
13147 ]),
13148 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13149 ];
13150 let mut encoded = Vec::new();
13151 write_protocol_v2_fetch_response(&mut encoded, §ions)
13152 .expect("test operation should succeed");
13153 encoded.extend_from_slice(b"tail");
13154
13155 let mut input = encoded.as_slice();
13156 assert_eq!(
13157 read_protocol_v2_fetch_response(ObjectFormat::Sha1, &mut input)
13158 .expect("test operation should succeed"),
13159 sections
13160 );
13161 assert_eq!(input, b"tail");
13162 }
13163
13164 #[test]
13165 fn protocol_v2_fetch_sideband_all_response_parses_sections_and_progress() {
13166 let frames = vec![
13167 PktLineFrame::Data(
13168 encode_sideband_packet(&SideBandPacket {
13169 channel: SideBandChannel::Data,
13170 data: b"acknowledgments\n".to_vec(),
13171 })
13172 .expect("test operation should succeed"),
13173 ),
13174 PktLineFrame::Data(
13175 encode_sideband_packet(&SideBandPacket {
13176 channel: SideBandChannel::Data,
13177 data: b"NAK\n".to_vec(),
13178 })
13179 .expect("test operation should succeed"),
13180 ),
13181 PktLineFrame::Data(
13182 encode_sideband_packet(&SideBandPacket {
13183 channel: SideBandChannel::Progress,
13184 data: b"keepalive\n".to_vec(),
13185 })
13186 .expect("test operation should succeed"),
13187 ),
13188 PktLineFrame::Delimiter,
13189 PktLineFrame::Data(
13190 encode_sideband_packet(&SideBandPacket {
13191 channel: SideBandChannel::Data,
13192 data: b"packfile\n".to_vec(),
13193 })
13194 .expect("test operation should succeed"),
13195 ),
13196 PktLineFrame::Data(b"\x01PACK".to_vec()),
13197 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
13198 PktLineFrame::Flush,
13199 ];
13200
13201 let response = parse_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &frames)
13202 .expect("test operation should succeed");
13203 assert_eq!(
13204 response,
13205 ProtocolV2FetchSidebandAllResponse {
13206 sections: vec![
13207 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13208 ProtocolV2FetchAcknowledgment::Nak
13209 ]),
13210 ProtocolV2FetchResponseSection::Packfile(vec![
13211 b"\x01PACK".to_vec(),
13212 b"\x02counting objects\n".to_vec(),
13213 ]),
13214 ],
13215 progress: vec![b"keepalive\n".to_vec()],
13216 }
13217 );
13218 assert_eq!(
13219 demux_protocol_v2_fetch_packfile(&response.sections)
13220 .expect("test operation should succeed"),
13221 Some(SideBandDemux {
13222 data: b"PACK".to_vec(),
13223 progress: vec![b"counting objects\n".to_vec()],
13224 })
13225 );
13226 }
13227
13228 #[test]
13229 fn protocol_v2_fetch_sideband_all_response_streams_round_trip() {
13230 let sections = vec![
13231 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13232 ProtocolV2FetchAcknowledgment::Nak,
13233 ]),
13234 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13235 ];
13236 let mut encoded = Vec::new();
13237 write_protocol_v2_fetch_sideband_all_response(&mut encoded, §ions)
13238 .expect("test operation should succeed");
13239 encoded.extend_from_slice(b"tail");
13240
13241 let mut input = encoded.as_slice();
13242 assert_eq!(
13243 read_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &mut input)
13244 .expect("test operation should succeed"),
13245 ProtocolV2FetchSidebandAllResponse {
13246 sections: sections.clone(),
13247 progress: Vec::new(),
13248 }
13249 );
13250 assert_eq!(input, b"tail");
13251
13252 let mut encoded = Vec::new();
13253 write_protocol_v2_fetch_sideband_all_response_with_response_end(&mut encoded, §ions)
13254 .expect("test operation should succeed");
13255 encoded.extend_from_slice(b"tail");
13256
13257 let mut input = encoded.as_slice();
13258 assert_eq!(
13259 read_protocol_v2_fetch_sideband_all_response_until_response_end(
13260 ObjectFormat::Sha1,
13261 &mut input,
13262 )
13263 .expect("test operation should succeed")
13264 .sections,
13265 sections
13266 );
13267 assert_eq!(input, b"tail");
13268 }
13269
13270 #[test]
13271 fn protocol_v2_fetch_sideband_all_response_rejects_malformed_sideband() {
13272 assert!(
13273 parse_protocol_v2_fetch_sideband_all_response(
13274 ObjectFormat::Sha1,
13275 &[
13276 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13277 PktLineFrame::Flush,
13278 ],
13279 )
13280 .is_err()
13281 );
13282 assert!(
13283 parse_protocol_v2_fetch_sideband_all_response(
13284 ObjectFormat::Sha1,
13285 &[
13286 PktLineFrame::Data(
13287 encode_sideband_packet(&SideBandPacket {
13288 channel: SideBandChannel::Fatal,
13289 data: b"remote died\n".to_vec(),
13290 })
13291 .expect("test operation should succeed"),
13292 ),
13293 PktLineFrame::Flush,
13294 ],
13295 )
13296 .is_err()
13297 );
13298 }
13299
13300 #[test]
13301 fn protocol_v2_object_info_response_parses_and_encodes_size_records() {
13302 let oid = ObjectId::from_hex(
13303 ObjectFormat::Sha1,
13304 "1111111111111111111111111111111111111111",
13305 )
13306 .expect("test operation should succeed");
13307 let frames = vec![
13308 PktLineFrame::Data(b"size\n".to_vec()),
13309 PktLineFrame::Data(b"1111111111111111111111111111111111111111 12345\n".to_vec()),
13310 PktLineFrame::Flush,
13311 ];
13312 let response = parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &frames)
13313 .expect("test operation should succeed");
13314 assert_eq!(
13315 response,
13316 ProtocolV2ObjectInfoResponse {
13317 size: true,
13318 records: vec![ProtocolV2ObjectInfoRecord { oid, size: 12345 }],
13319 }
13320 );
13321 assert_eq!(
13322 encode_protocol_v2_object_info_response(&response)
13323 .expect("test operation should succeed"),
13324 frames
13325 );
13326 }
13327
13328 #[test]
13329 fn protocol_v2_object_info_response_streams_and_exchanges() {
13330 let request = ProtocolV2ObjectInfoRequest {
13331 size: true,
13332 oids: vec![
13333 ObjectId::from_hex(
13334 ObjectFormat::Sha1,
13335 "1111111111111111111111111111111111111111",
13336 )
13337 .expect("test operation should succeed"),
13338 ],
13339 };
13340 let response = ProtocolV2ObjectInfoResponse {
13341 size: true,
13342 records: vec![ProtocolV2ObjectInfoRecord {
13343 oid: request.oids[0].clone(),
13344 size: 7,
13345 }],
13346 };
13347
13348 let mut encoded = Vec::new();
13349 write_protocol_v2_object_info_response(&mut encoded, &response)
13350 .expect("test operation should succeed");
13351 encoded.extend_from_slice(b"tail");
13352 let mut input = encoded.as_slice();
13353 assert_eq!(
13354 read_protocol_v2_object_info_response(ObjectFormat::Sha1, &mut input)
13355 .expect("test operation should succeed"),
13356 response
13357 );
13358 assert_eq!(input, b"tail");
13359
13360 let mut response_bytes = Vec::new();
13361 write_protocol_v2_object_info_response(&mut response_bytes, &response)
13362 .expect("test operation should succeed");
13363 let mut input = response_bytes.as_slice();
13364 let mut output = Vec::new();
13365 assert_eq!(
13366 exchange_protocol_v2_object_info(
13367 ObjectFormat::Sha1,
13368 &mut input,
13369 &mut output,
13370 &request,
13371 )
13372 .expect("test operation should succeed"),
13373 response
13374 );
13375 assert!(input.is_empty());
13376 let mut output_read = output.as_slice();
13377 assert_eq!(
13378 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut output_read)
13379 .expect("test operation should succeed"),
13380 request
13381 );
13382 }
13383
13384 #[test]
13385 fn protocol_v2_object_info_response_rejects_malformed_records() {
13386 assert!(parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &[]).is_err());
13387 assert!(
13388 parse_protocol_v2_object_info_response(
13389 ObjectFormat::Sha1,
13390 &[PktLineFrame::Data(b"size\n".to_vec())],
13391 )
13392 .is_err()
13393 );
13394 assert!(
13395 parse_protocol_v2_object_info_response(
13396 ObjectFormat::Sha1,
13397 &[PktLineFrame::Data(b"type\n".to_vec()), PktLineFrame::Flush,],
13398 )
13399 .is_err()
13400 );
13401 assert!(
13402 parse_protocol_v2_object_info_response(
13403 ObjectFormat::Sha1,
13404 &[
13405 PktLineFrame::Data(b"size\n".to_vec()),
13406 PktLineFrame::Data(
13407 b"1111111111111111111111111111111111111111 not-a-size\n".to_vec()
13408 ),
13409 PktLineFrame::Flush,
13410 ],
13411 )
13412 .is_err()
13413 );
13414 assert!(
13415 parse_protocol_v2_object_info_response(
13416 ObjectFormat::Sha1,
13417 &[
13418 PktLineFrame::Data(b"size\n".to_vec()),
13419 PktLineFrame::Delimiter,
13420 PktLineFrame::Flush,
13421 ],
13422 )
13423 .is_err()
13424 );
13425 assert!(
13426 encode_protocol_v2_object_info_response(&ProtocolV2ObjectInfoResponse {
13427 size: false,
13428 records: Vec::new(),
13429 })
13430 .is_err()
13431 );
13432 }
13433
13434 #[test]
13435 fn protocol_v2_fetch_response_reads_stateless_response_end() {
13436 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13437 ProtocolV2FetchAcknowledgment::Nak,
13438 ])];
13439 let mut encoded = Vec::new();
13440 write_protocol_v2_fetch_response_with_response_end(&mut encoded, §ions)
13441 .expect("test operation should succeed");
13442 encoded.extend_from_slice(b"tail");
13443
13444 let mut input = encoded.as_slice();
13445 assert_eq!(
13446 read_protocol_v2_fetch_response_until_response_end(ObjectFormat::Sha1, &mut input)
13447 .expect("test operation should succeed"),
13448 sections
13449 );
13450 assert_eq!(input, b"tail");
13451 assert!(
13452 parse_protocol_v2_fetch_response(
13453 ObjectFormat::Sha1,
13454 &[
13455 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13456 PktLineFrame::ResponseEnd,
13457 ],
13458 )
13459 .is_err()
13460 );
13461 }
13462
13463 #[test]
13464 fn protocol_v2_fetch_exchange_writes_request_and_reads_response() {
13465 let want = ObjectId::from_hex(
13466 ObjectFormat::Sha1,
13467 "1111111111111111111111111111111111111111",
13468 )
13469 .expect("test operation should succeed");
13470 let request = ProtocolV2FetchRequest {
13471 wants: vec![want],
13472 thin_pack: true,
13473 done: true,
13474 ..ProtocolV2FetchRequest::default()
13475 };
13476 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13477 ProtocolV2FetchAcknowledgment::Nak,
13478 ])];
13479 let mut response = Vec::new();
13480 write_protocol_v2_fetch_response(&mut response, §ions)
13481 .expect("test operation should succeed");
13482
13483 let mut input = response.as_slice();
13484 let mut output = Vec::new();
13485 assert_eq!(
13486 exchange_protocol_v2_fetch(ObjectFormat::Sha1, &mut input, &mut output, &request)
13487 .expect("test operation should succeed"),
13488 sections
13489 );
13490 assert!(input.is_empty());
13491 let mut output_read = output.as_slice();
13492 assert_eq!(
13493 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut output_read)
13494 .expect("test operation should succeed"),
13495 request
13496 );
13497 }
13498
13499 #[test]
13500 fn protocol_v2_fetch_packfile_demuxes_sideband_section() {
13501 let sections = vec![
13502 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13503 ProtocolV2FetchAcknowledgment::Nak,
13504 ]),
13505 ProtocolV2FetchResponseSection::Packfile(vec![
13506 b"\x01PACK".to_vec(),
13507 b"\x02counting objects\n".to_vec(),
13508 b"\x01 bytes".to_vec(),
13509 b"\x02done\n".to_vec(),
13510 ]),
13511 ];
13512
13513 assert_eq!(
13514 demux_protocol_v2_fetch_packfile(§ions).expect("test operation should succeed"),
13515 Some(SideBandDemux {
13516 data: b"PACK bytes".to_vec(),
13517 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
13518 })
13519 );
13520 assert_eq!(
13521 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Acknowledgments(
13522 vec![ProtocolV2FetchAcknowledgment::Nak],
13523 )])
13524 .expect("test operation should succeed"),
13525 None
13526 );
13527 }
13528
13529 #[test]
13530 fn protocol_v2_fetch_packfile_demux_rejects_duplicate_or_bad_sideband() {
13531 assert!(
13532 demux_protocol_v2_fetch_packfile(&[
13533 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK".to_vec()]),
13534 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01more".to_vec()]),
13535 ])
13536 .is_err()
13537 );
13538 assert!(
13539 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13540 b"\x03remote died\n".to_vec()
13541 ])])
13542 .is_err()
13543 );
13544 assert!(
13545 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13546 b"\x04bad".to_vec()
13547 ])])
13548 .is_err()
13549 );
13550 }
13551
13552 #[test]
13553 fn protocol_v2_fetch_response_rejects_malformed_sections() {
13554 assert!(
13555 parse_protocol_v2_fetch_response(
13556 ObjectFormat::Sha1,
13557 &[PktLineFrame::Data(b"acknowledgments\n".to_vec())],
13558 )
13559 .is_err()
13560 );
13561 assert!(
13562 parse_protocol_v2_fetch_response(
13563 ObjectFormat::Sha1,
13564 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
13565 )
13566 .is_err()
13567 );
13568 assert!(
13569 parse_protocol_v2_fetch_response(
13570 ObjectFormat::Sha1,
13571 &[
13572 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13573 PktLineFrame::Data(b"ACK not-an-oid\n".to_vec()),
13574 PktLineFrame::Flush,
13575 ],
13576 )
13577 .is_err()
13578 );
13579 assert!(
13580 parse_protocol_v2_fetch_response(
13581 ObjectFormat::Sha1,
13582 &[
13583 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13584 PktLineFrame::Data(b"https://example.invalid/pack-a.pack\n".to_vec()),
13585 PktLineFrame::Flush,
13586 ],
13587 )
13588 .is_err()
13589 );
13590 assert!(
13591 parse_protocol_v2_fetch_response(
13592 ObjectFormat::Sha1,
13593 &[
13594 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13595 PktLineFrame::Data(
13596 b"not-a-hash https://example.invalid/pack-a.pack\n".to_vec()
13597 ),
13598 PktLineFrame::Flush,
13599 ],
13600 )
13601 .is_err()
13602 );
13603 assert!(
13604 encode_protocol_v2_fetch_response(&[ProtocolV2FetchResponseSection::WantedRefs(vec![
13605 ProtocolV2FetchWantedRef {
13606 oid: ObjectId::from_hex(
13607 ObjectFormat::Sha1,
13608 "1111111111111111111111111111111111111111",
13609 )
13610 .expect("test operation should succeed"),
13611 name: "bad ref".into(),
13612 }
13613 ])])
13614 .is_err()
13615 );
13616 }
13617
13618 #[test]
13619 fn protocol_v2_ls_refs_response_bridges_into_ref_advertisement_set() {
13620 let head = ObjectId::from_hex(
13621 ObjectFormat::Sha1,
13622 "1111111111111111111111111111111111111111",
13623 )
13624 .expect("test operation should succeed");
13625 let tag = ObjectId::from_hex(
13626 ObjectFormat::Sha1,
13627 "2222222222222222222222222222222222222222",
13628 )
13629 .expect("test operation should succeed");
13630 let tag_peeled = ObjectId::from_hex(
13631 ObjectFormat::Sha1,
13632 "3333333333333333333333333333333333333333",
13633 )
13634 .expect("test operation should succeed");
13635 let frames = vec![
13636 PktLineFrame::Data(
13637 b"1111111111111111111111111111111111111111 HEAD symref-target:refs/heads/main\n"
13638 .to_vec(),
13639 ),
13640 PktLineFrame::Data(
13641 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
13642 ),
13643 PktLineFrame::Data(
13644 b"2222222222222222222222222222222222222222 refs/tags/v1 peeled:3333333333333333333333333333333333333333\n"
13645 .to_vec(),
13646 ),
13647 PktLineFrame::Flush,
13648 ];
13649
13650 let set = parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13651 ObjectFormat::Sha1,
13652 &frames,
13653 )
13654 .expect("test operation should succeed");
13655 assert_eq!(
13656 set,
13657 RefAdvertisementSet {
13658 protocol: ProtocolVersion::V2,
13659 refs: vec![
13660 RefAdvertisement {
13661 oid: head.clone(),
13662 name: "HEAD".into(),
13663 capabilities: vec![Capability {
13664 name: "symref".into(),
13665 value: Some("HEAD:refs/heads/main".into()),
13666 }],
13667 },
13668 RefAdvertisement {
13669 oid: head,
13670 name: "refs/heads/main".into(),
13671 capabilities: Vec::new(),
13672 },
13673 RefAdvertisement {
13674 oid: tag,
13675 name: "refs/tags/v1".into(),
13676 capabilities: Vec::new(),
13677 },
13678 RefAdvertisement {
13679 oid: tag_peeled,
13680 name: "refs/tags/v1^{}".into(),
13681 capabilities: Vec::new(),
13682 },
13683 ],
13684 shallow: Vec::new(),
13685 }
13686 );
13687
13688 let mut encoded = Vec::new();
13690 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
13691 encoded.extend_from_slice(b"tail");
13692 let mut input = encoded.as_slice();
13693 assert_eq!(
13694 read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13695 ObjectFormat::Sha1,
13696 &mut input,
13697 )
13698 .expect("test operation should succeed"),
13699 set,
13700 );
13701 assert_eq!(input, b"tail");
13702 }
13703
13704 #[test]
13705 fn protocol_v2_ls_refs_records_bridge_unborn_head_symref_and_empty() {
13706 let records = vec![ProtocolV2LsRefsRecord::Unborn {
13709 name: "HEAD".into(),
13710 symref_target: Some("refs/heads/main".into()),
13711 attributes: Vec::new(),
13712 }];
13713 assert!(protocol_v2_ls_refs_records_to_ref_advertisement_set(&records).is_err());
13714
13715 assert_eq!(
13717 protocol_v2_ls_refs_records_to_ref_advertisement_set(&[])
13718 .expect("test operation should succeed"),
13719 RefAdvertisementSet {
13720 protocol: ProtocolVersion::V2,
13721 refs: Vec::new(),
13722 shallow: Vec::new(),
13723 }
13724 );
13725
13726 let main = ObjectId::from_hex(
13729 ObjectFormat::Sha1,
13730 "4444444444444444444444444444444444444444",
13731 )
13732 .expect("test operation should succeed");
13733 let records = vec![
13734 ProtocolV2LsRefsRecord::Unborn {
13735 name: "HEAD".into(),
13736 symref_target: Some("refs/heads/main".into()),
13737 attributes: Vec::new(),
13738 },
13739 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13740 oid: main.clone(),
13741 name: "refs/heads/main".into(),
13742 peeled: None,
13743 symref_target: None,
13744 attributes: Vec::new(),
13745 }),
13746 ];
13747 let set = protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
13748 .expect("test operation should succeed");
13749 assert_eq!(
13750 set,
13751 RefAdvertisementSet {
13752 protocol: ProtocolVersion::V2,
13753 refs: vec![RefAdvertisement {
13754 oid: main,
13755 name: "refs/heads/main".into(),
13756 capabilities: vec![Capability {
13757 name: "symref".into(),
13758 value: Some("HEAD:refs/heads/main".into()),
13759 }],
13760 }],
13761 shallow: Vec::new(),
13762 }
13763 );
13764 }
13765}