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};
7use std::sync::RwLock;
8
9static PACKET_TRACE_IDENTITY: RwLock<Option<String>> = RwLock::new(None);
22
23pub fn set_packet_trace_identity(prog: &str) {
26 if let Ok(mut guard) = PACKET_TRACE_IDENTITY.write() {
27 *guard = Some(prog.to_string());
28 }
29}
30
31fn packet_trace_prefix() -> String {
32 PACKET_TRACE_IDENTITY
33 .read()
34 .ok()
35 .and_then(|guard| guard.clone())
36 .unwrap_or_else(|| "git".to_string())
37}
38
39fn packet_trace_sink() -> Option<Box<dyn Write>> {
43 let value = std::env::var("GIT_TRACE_PACKET").ok()?;
44 let lower = value.to_ascii_lowercase();
45 match lower.as_str() {
46 "" | "0" | "false" => None,
47 "1" | "2" | "true" => Some(Box::new(std::io::stderr())),
48 _ => {
49 if std::path::Path::new(&value).is_absolute() {
50 std::fs::OpenOptions::new()
51 .create(true)
52 .append(true)
53 .open(&value)
54 .ok()
55 .map(|f| Box::new(f) as Box<dyn Write>)
56 } else {
57 None
58 }
59 }
60 }
61}
62
63fn packet_trace_enabled() -> bool {
65 match std::env::var("GIT_TRACE_PACKET") {
66 Ok(value) => {
67 let lower = value.to_ascii_lowercase();
68 !matches!(lower.as_str(), "" | "0" | "false")
69 }
70 Err(_) => false,
71 }
72}
73
74fn packet_trace(data: &[u8], is_write: bool) {
80 if !packet_trace_enabled() {
81 return;
82 }
83 let Some(mut sink) = packet_trace_sink() else {
84 return;
85 };
86 let rendered: Vec<u8> = if data.starts_with(b"PACK") || data.starts_with(b"\x01PACK") {
91 b"PACK ...".to_vec()
92 } else {
93 data.to_vec()
94 };
95
96 let mut out = format!(
97 "packet: {:>12}{} ",
98 packet_trace_prefix(),
99 if is_write { '>' } else { '<' }
100 );
101 for &byte in &rendered {
102 if byte == b'\n' {
103 continue;
104 }
105 if (0x20..=0x7e).contains(&byte) {
106 out.push(byte as char);
107 } else {
108 out.push_str(&format!("\\{byte:o}"));
109 }
110 }
111 out.push('\n');
112 let _ = sink.write_all(out.as_bytes());
113 let _ = sink.flush();
114}
115
116pub fn trace_packet_read_payload(payload: &[u8]) {
117 packet_trace(payload, false);
118}
119
120pub fn trace_packet_write_payload(payload: &[u8]) {
121 packet_trace(payload, true);
122}
123
124fn packet_trace_frame(frame: &PktLineFrame, is_write: bool) {
127 if !packet_trace_enabled() {
128 return;
129 }
130 match frame {
131 PktLineFrame::Data(payload) => packet_trace(payload, is_write),
132 PktLineFrame::Flush => packet_trace(b"0000", is_write),
133 PktLineFrame::Delimiter => packet_trace(b"0001", is_write),
134 PktLineFrame::ResponseEnd => packet_trace(b"0002", is_write),
135 }
136}
137
138pub const PKT_LINE_MAX_LEN: usize = 65_520;
139
140pub const PKT_LINE_MAX_PAYLOAD_LEN: usize = PKT_LINE_MAX_LEN - 4;
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum ProtocolVersion {
144 V0,
145 V1,
146 V2,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct PktLine(pub Vec<u8>);
151
152impl PktLine {
153 pub fn encode(&self) -> Vec<u8> {
154 encode_pkt_line_payload(&self.0)
155 }
156
157 pub fn try_encode(&self) -> Result<Vec<u8>> {
158 validate_pkt_line_payload(&self.0)?;
159 Ok(self.encode())
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub enum PktLineFrame {
165 Data(Vec<u8>),
166 Flush,
167 Delimiter,
168 ResponseEnd,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct ProtocolErrorLine {
173 pub message: String,
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum GitService {
178 UploadPack,
179 ReceivePack,
180 UploadArchive,
181}
182
183impl GitService {
184 pub fn as_str(self) -> &'static str {
185 match self {
186 Self::UploadPack => "git-upload-pack",
187 Self::ReceivePack => "git-receive-pack",
188 Self::UploadArchive => "git-upload-archive",
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct RefSpec {
195 pub force: bool,
196 pub negative: bool,
197 pub src: Option<String>,
198 pub dst: Option<String>,
199 pub pattern: bool,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct FetchHeadRecord {
204 pub oid: ObjectId,
205 pub not_for_merge: bool,
206 pub description: String,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct FetchRefUpdate {
211 pub src: String,
212 pub dst: Option<String>,
213 pub oid: ObjectId,
214 pub not_for_merge: bool,
215 pub force: bool,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct PushSourceRef {
220 pub name: String,
221 pub oid: ObjectId,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum SideBandChannel {
226 Data,
227 Progress,
228 Fatal,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct SideBandPacket {
233 pub channel: SideBandChannel,
234 pub data: Vec<u8>,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Default)]
238pub struct SideBandDemux {
239 pub data: Vec<u8>,
240 pub progress: Vec<Vec<u8>>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Default)]
244pub struct UploadArchiveRequest {
245 pub arguments: Vec<String>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq)]
249pub enum UploadArchiveResponse {
250 Ack { sideband: Vec<SideBandPacket> },
251 Nack { message: String },
252}
253
254impl PktLineFrame {
255 pub fn data(payload: impl Into<Vec<u8>>) -> Result<Self> {
256 let payload = payload.into();
257 validate_pkt_line_payload(&payload)?;
258 Ok(Self::Data(payload))
259 }
260
261 pub fn encode(&self) -> Vec<u8> {
262 match self {
263 Self::Data(payload) => encode_pkt_line_payload(payload),
264 Self::Flush => b"0000".to_vec(),
265 Self::Delimiter => b"0001".to_vec(),
266 Self::ResponseEnd => b"0002".to_vec(),
267 }
268 }
269
270 pub fn try_encode(&self) -> Result<Vec<u8>> {
271 match self {
272 Self::Data(payload) => try_encode_pkt_line_payload(payload),
273 Self::Flush | Self::Delimiter | Self::ResponseEnd => Ok(self.encode()),
274 }
275 }
276
277 pub fn parse(input: &[u8]) -> Result<(Self, usize)> {
278 if input.len() < 4 {
279 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
280 }
281 let len = parse_pkt_len(&input[..4])?;
282 match len {
283 0 => Ok((Self::Flush, 4)),
284 1 => Ok((Self::Delimiter, 4)),
285 2 => Ok((Self::ResponseEnd, 4)),
286 3 => Err(GitError::InvalidFormat(
287 "reserved pkt-line length 0003".into(),
288 )),
289 4..=PKT_LINE_MAX_LEN => {
290 if input.len() < len {
291 return Err(GitError::InvalidFormat(format!(
292 "truncated pkt-line payload: expected {} bytes, got {}",
293 len - 4,
294 input.len().saturating_sub(4)
295 )));
296 }
297 Ok((Self::Data(input[4..len].to_vec()), len))
298 }
299 _ => Err(GitError::InvalidFormat(format!(
300 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
301 ))),
302 }
303 }
304}
305
306fn validate_pkt_line_payload(payload: &[u8]) -> Result<()> {
307 if payload.len() > PKT_LINE_MAX_PAYLOAD_LEN {
308 return Err(GitError::InvalidFormat(format!(
309 "pkt-line payload exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
310 )));
311 }
312 Ok(())
313}
314
315fn pkt_line_header(len: usize) -> [u8; 4] {
316 const HEX: &[u8; 16] = b"0123456789abcdef";
317 [
318 HEX[(len >> 12) & 0xf],
319 HEX[(len >> 8) & 0xf],
320 HEX[(len >> 4) & 0xf],
321 HEX[len & 0xf],
322 ]
323}
324
325fn encode_pkt_line_payload(payload: &[u8]) -> Vec<u8> {
326 let len = payload.len() + 4;
327 let mut out = Vec::with_capacity(len);
328 out.extend_from_slice(&pkt_line_header(len));
329 out.extend_from_slice(payload);
330 out
331}
332
333fn try_encode_pkt_line_payload(payload: &[u8]) -> Result<Vec<u8>> {
334 validate_pkt_line_payload(payload)?;
335 Ok(encode_pkt_line_payload(payload))
336}
337
338pub fn parse_pkt_line_stream(mut input: &[u8]) -> Result<Vec<PktLineFrame>> {
339 let mut frames = Vec::new();
340 while !input.is_empty() {
341 let (frame, consumed) = PktLineFrame::parse(input)?;
342 frames.push(frame);
343 input = &input[consumed..];
344 }
345 Ok(frames)
346}
347
348fn parse_pkt_line_frames_until_flush_from(mut input: &[u8]) -> Result<(Vec<PktLineFrame>, usize)> {
349 let mut frames = Vec::new();
350 let mut total = 0usize;
351 loop {
352 if input.is_empty() {
353 return Err(GitError::InvalidFormat(
354 "pkt-line stream ended before flush".into(),
355 ));
356 }
357 let (frame, consumed) = PktLineFrame::parse(input)?;
358 total += consumed;
359 let done = matches!(frame, PktLineFrame::Flush);
360 frames.push(frame);
361 input = &input[consumed..];
362 if done {
363 return Ok((frames, total));
364 }
365 }
366}
367
368pub fn read_pkt_line_frame(reader: &mut impl Read) -> Result<Option<PktLineFrame>> {
369 let mut header = [0u8; 4];
370 let mut read = 0usize;
371 while read < header.len() {
372 match reader.read(&mut header[read..]) {
373 Ok(0) if read == 0 => return Ok(None),
374 Ok(0) => {
375 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
376 }
377 Ok(n) => read += n,
378 Err(err) if err.kind() == ErrorKind::Interrupted => {}
379 Err(err) => return Err(err.into()),
380 }
381 }
382
383 let len = parse_pkt_len(&header)?;
384 let frame = match len {
385 0 => PktLineFrame::Flush,
386 1 => PktLineFrame::Delimiter,
387 2 => PktLineFrame::ResponseEnd,
388 3 => {
389 return Err(GitError::InvalidFormat(
390 "reserved pkt-line length 0003".into(),
391 ));
392 }
393 4..=PKT_LINE_MAX_LEN => {
394 let mut payload = vec![0; len - 4];
395 reader.read_exact(&mut payload)?;
396 PktLineFrame::Data(payload)
397 }
398 _ => {
399 return Err(GitError::InvalidFormat(format!(
400 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
401 )));
402 }
403 };
404 packet_trace_frame(&frame, false);
405 Ok(Some(frame))
406}
407
408pub fn read_pkt_line_frames(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
409 let mut frames = Vec::new();
410 while let Some(frame) = read_pkt_line_frame(reader)? {
411 frames.push(frame);
412 }
413 Ok(frames)
414}
415
416pub fn read_pkt_line_frames_until_flush(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
417 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::Flush))
418}
419
420pub fn read_pkt_line_frames_until_response_end(
421 reader: &mut impl Read,
422) -> Result<Vec<PktLineFrame>> {
423 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::ResponseEnd))
424}
425
426fn read_pkt_line_frames_until_control(
427 reader: &mut impl Read,
428 stop: impl Fn(&PktLineFrame) -> bool,
429) -> Result<Vec<PktLineFrame>> {
430 let mut frames = Vec::new();
431 loop {
432 let Some(frame) = read_pkt_line_frame(reader)? else {
433 return Err(GitError::InvalidFormat(
434 "pkt-line stream ended before control packet".into(),
435 ));
436 };
437 let done = stop(&frame);
438 frames.push(frame);
439 if done {
440 return Ok(frames);
441 }
442 }
443}
444
445pub fn write_pkt_line_frame(writer: &mut impl Write, frame: &PktLineFrame) -> Result<()> {
446 match frame {
447 PktLineFrame::Data(payload) => write_pkt_line_payload(writer, payload)?,
449 PktLineFrame::Flush => {
450 packet_trace(b"0000", true);
451 writer.write_all(b"0000")?;
452 }
453 PktLineFrame::Delimiter => {
454 packet_trace(b"0001", true);
455 writer.write_all(b"0001")?;
456 }
457 PktLineFrame::ResponseEnd => {
458 packet_trace(b"0002", true);
459 writer.write_all(b"0002")?;
460 }
461 }
462 Ok(())
463}
464
465pub fn write_pkt_line_payload(writer: &mut impl Write, payload: &[u8]) -> Result<()> {
466 validate_pkt_line_payload(payload)?;
467 packet_trace(payload, true);
468 let len = payload.len() + 4;
469 writer.write_all(&pkt_line_header(len))?;
470 writer.write_all(payload)?;
471 Ok(())
472}
473
474pub fn write_pkt_line_frames(writer: &mut impl Write, frames: &[PktLineFrame]) -> Result<()> {
475 for frame in frames {
476 write_pkt_line_frame(writer, frame)?;
477 }
478 Ok(())
479}
480
481pub fn parse_error_line(payload: &[u8]) -> Result<ProtocolErrorLine> {
482 let text = parse_protocol_v2_line_text("protocol error line", payload)?;
483 let Some(message) = text.strip_prefix("ERR ") else {
484 return Err(GitError::InvalidFormat(
485 "protocol error line must start with ERR".into(),
486 ));
487 };
488 validate_protocol_error_message(message)?;
489 Ok(ProtocolErrorLine {
490 message: message.to_string(),
491 })
492}
493
494pub fn encode_error_line(error: &ProtocolErrorLine) -> Result<Vec<u8>> {
495 validate_protocol_error_message(&error.message)?;
496 Ok(line_from_str(&format!("ERR {}", error.message)))
497}
498
499pub fn parse_error_frame(frame: &PktLineFrame) -> Result<Option<ProtocolErrorLine>> {
500 match frame {
501 PktLineFrame::Data(payload) if trim_trailing_lf(payload).starts_with(b"ERR ") => {
502 parse_error_line(payload).map(Some)
503 }
504 PktLineFrame::Data(_)
505 | PktLineFrame::Flush
506 | PktLineFrame::Delimiter
507 | PktLineFrame::ResponseEnd => Ok(None),
508 }
509}
510
511pub fn read_error_line(reader: &mut impl Read) -> Result<ProtocolErrorLine> {
512 let Some(frame) = read_pkt_line_frame(reader)? else {
513 return Err(GitError::InvalidFormat(
514 "pkt-line stream ended before protocol error line".into(),
515 ));
516 };
517 match frame {
518 PktLineFrame::Data(payload) => parse_error_line(&payload),
519 _ => Err(GitError::InvalidFormat(
520 "protocol error line must be a data packet".into(),
521 )),
522 }
523}
524
525pub fn write_error_line(writer: &mut impl Write, error: &ProtocolErrorLine) -> Result<()> {
526 write_pkt_line_frame(writer, &PktLineFrame::data(encode_error_line(error)?)?)
527}
528
529pub fn parse_git_service(value: &str) -> Result<GitService> {
530 match value {
531 "git-upload-pack" => Ok(GitService::UploadPack),
532 "git-receive-pack" => Ok(GitService::ReceivePack),
533 "git-upload-archive" => Ok(GitService::UploadArchive),
534 other => Err(GitError::InvalidFormat(format!(
535 "unsupported git service {other}"
536 ))),
537 }
538}
539
540pub fn parse_refspec(value: &str) -> Result<RefSpec> {
541 validate_refspec_value(value)?;
542 let (force, value) = value
543 .strip_prefix('+')
544 .map_or((false, value), |value| (true, value));
545 let (negative, value) = value
546 .strip_prefix('^')
547 .map_or((false, value), |value| (true, value));
548 if force && negative {
549 return Err(GitError::InvalidFormat(
550 "negative refspec must not be forced".into(),
551 ));
552 }
553 let (src, dst) = if negative {
554 if value.contains(':') {
555 return Err(GitError::InvalidFormat(
556 "negative refspec must not have a destination".into(),
557 ));
558 }
559 (Some(value), None)
560 } else if let Some((src, dst)) = value.split_once(':') {
561 (non_empty(src), non_empty(dst))
562 } else {
563 (Some(value), None)
564 };
565 if src.is_none() && dst.is_none() && value != ":" {
566 return Err(GitError::InvalidFormat(
567 "refspec must include a source or destination".into(),
568 ));
569 }
570 if negative && src.is_none() {
571 return Err(GitError::InvalidFormat(
572 "negative refspec is missing a source".into(),
573 ));
574 }
575 if let Some(src) = src {
576 validate_refspec_endpoint("refspec source", src)?;
577 }
578 if let Some(dst) = dst {
579 validate_refspec_endpoint("refspec destination", dst)?;
580 }
581 let src_pattern_count = src.map(count_refspec_wildcards).unwrap_or(0);
582 let dst_pattern_count = dst.map(count_refspec_wildcards).unwrap_or(0);
583 if src_pattern_count > 1 || dst_pattern_count > 1 {
584 return Err(GitError::InvalidFormat(
585 "refspec endpoint has too many wildcards".into(),
586 ));
587 }
588 if dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
589 return Err(GitError::InvalidFormat(
590 "refspec wildcard must appear in both source and destination".into(),
591 ));
592 }
593 Ok(RefSpec {
594 force,
595 negative,
596 src: src.map(str::to_string),
597 dst: dst.map(str::to_string),
598 pattern: src_pattern_count == 1 || dst_pattern_count == 1,
599 })
600}
601
602pub fn encode_refspec(refspec: &RefSpec) -> Result<String> {
603 validate_refspec_shape(refspec)?;
604 let mut out = String::new();
605 if refspec.force {
606 out.push('+');
607 }
608 if refspec.negative {
609 out.push('^');
610 }
611 if let Some(src) = &refspec.src {
612 out.push_str(src);
613 }
614 if !refspec.negative && refspec.src.is_none() && refspec.dst.is_none() {
615 out.push(':');
616 } else if !refspec.negative && refspec.dst.is_some() {
617 out.push(':');
618 if let Some(dst) = &refspec.dst {
619 out.push_str(dst);
620 }
621 }
622 Ok(out)
623}
624
625pub fn refspec_matches_source(refspec: &RefSpec, source: &str) -> Result<bool> {
626 Ok(refspec_map_source(refspec, source)?.is_some())
627}
628
629pub fn refspec_map_source(refspec: &RefSpec, source: &str) -> Result<Option<String>> {
630 validate_refspec_shape(refspec)?;
631 validate_refspec_endpoint("refspec match source", source)?;
632 let Some(src) = refspec.src.as_deref() else {
633 return Ok(None);
634 };
635 if refspec.pattern {
636 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
637 return Ok(None);
638 };
639 let Some(middle) = source
640 .strip_prefix(src_prefix)
641 .and_then(|value| value.strip_suffix(src_suffix))
642 else {
643 return Ok(None);
644 };
645 if let Some(dst) = refspec.dst.as_deref() {
646 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
647 GitError::InvalidFormat("pattern refspec destination is missing wildcard".into())
648 })?;
649 return Ok(Some(format!("{dst_prefix}{middle}{dst_suffix}")));
650 }
651 return Ok(Some(source.to_string()));
652 }
653 if src == source {
654 return Ok(Some(
655 refspec.dst.clone().unwrap_or_else(|| source.to_string()),
656 ));
657 }
658 Ok(None)
659}
660
661pub fn fetch_head_ref_description(refname: &str) -> Result<String> {
662 validate_fetch_head_description_field(refname)?;
663 if refname == "HEAD" {
667 Ok(String::new())
668 } else if let Some(branch) = refname.strip_prefix("refs/heads/") {
669 Ok(format!("branch '{branch}'"))
670 } else if let Some(tag) = refname.strip_prefix("refs/tags/") {
671 Ok(format!("tag '{tag}'"))
672 } else if let Some(rest) = refname.strip_prefix("refs/remotes/") {
673 Ok(format!("remote-tracking branch '{rest}'"))
674 } else {
675 Ok(format!("'{refname}'"))
676 }
677}
678
679pub fn fetch_head_remote_description(refname: &str, remote: &str) -> Result<String> {
680 validate_fetch_head_description_field(remote)?;
681 let what = fetch_head_ref_description(refname)?;
684 if what.is_empty() {
685 Ok(remote.to_string())
686 } else {
687 Ok(format!("{what} of {remote}"))
688 }
689}
690
691pub fn parse_fetch_head(format: ObjectFormat, input: &[u8]) -> Result<Vec<FetchHeadRecord>> {
692 if input.is_empty() {
693 return Ok(Vec::new());
694 }
695 input
696 .split_inclusive(|byte| *byte == b'\n')
697 .map(|line| parse_fetch_head_record(format, line))
698 .collect()
699}
700
701pub fn encode_fetch_head(records: &[FetchHeadRecord]) -> Result<Vec<u8>> {
702 let mut out = Vec::new();
703 for record in records {
704 validate_fetch_head_description_field(&record.description)?;
705 out.extend_from_slice(record.oid.to_string().as_bytes());
706 out.push(b'\t');
707 if record.not_for_merge {
708 out.extend_from_slice(b"not-for-merge");
709 }
710 out.push(b'\t');
711 out.extend_from_slice(record.description.as_bytes());
712 out.push(b'\n');
713 }
714 Ok(out)
715}
716
717pub fn read_fetch_head(
718 format: ObjectFormat,
719 reader: &mut impl Read,
720) -> Result<Vec<FetchHeadRecord>> {
721 let mut input = Vec::new();
722 reader.read_to_end(&mut input)?;
723 parse_fetch_head(format, &input)
724}
725
726pub fn write_fetch_head(writer: &mut impl Write, records: &[FetchHeadRecord]) -> Result<()> {
727 for record in records {
728 validate_fetch_head_description_field(&record.description)?;
729 writer.write_all(record.oid.to_string().as_bytes())?;
730 writer.write_all(b"\t")?;
731 if record.not_for_merge {
732 writer.write_all(b"not-for-merge")?;
733 }
734 writer.write_all(b"\t")?;
735 writer.write_all(record.description.as_bytes())?;
736 writer.write_all(b"\n")?;
737 }
738 Ok(())
739}
740
741fn find_advertised_ref_by_name_abbrev<'a>(
747 refs: &'a [RefAdvertisement],
748 name: &str,
749) -> Option<&'a RefAdvertisement> {
750 let mut best: Option<(&RefAdvertisement, usize)> = None;
751 for reference in refs {
752 let score = fetch_refname_match_score(name, &reference.name);
753 if score > best.map(|(_, score)| score).unwrap_or(0) {
754 best = Some((reference, score));
755 }
756 }
757 best.map(|(reference, _)| reference)
758}
759
760fn fetch_refname_match_score(abbrev: &str, full: &str) -> usize {
763 let expansions = [
764 abbrev.to_string(),
765 format!("refs/{abbrev}"),
766 format!("refs/tags/{abbrev}"),
767 format!("refs/heads/{abbrev}"),
768 format!("refs/remotes/{abbrev}"),
769 format!("refs/remotes/{abbrev}/HEAD"),
770 ];
771 for (index, candidate) in expansions.iter().enumerate() {
772 if candidate == full {
773 return expansions.len() - index;
774 }
775 }
776 0
777}
778
779pub fn refname_matches(abbrev: &str, full: &str) -> bool {
784 fetch_refname_match_score(abbrev, full) > 0
785}
786
787fn fetch_local_ref_name(name: &str) -> String {
791 if name.starts_with("refs/") {
792 name.to_string()
793 } else if name.starts_with("heads/")
794 || name.starts_with("tags/")
795 || name.starts_with("remotes/")
796 {
797 format!("refs/{name}")
798 } else {
799 format!("refs/heads/{name}")
800 }
801}
802
803pub fn plan_fetch_ref_updates(
804 refs: &[RefAdvertisement],
805 refspecs: &[RefSpec],
806 auto_follow_tags: bool,
807) -> Result<Vec<FetchRefUpdate>> {
808 let negative = refspecs
809 .iter()
810 .filter(|refspec| refspec.negative)
811 .collect::<Vec<_>>();
812 let mut updates = Vec::new();
813 for refspec in refspecs.iter().filter(|refspec| !refspec.negative) {
814 validate_refspec_shape(refspec)?;
815 let Some(src) = refspec.src.as_deref() else {
816 return Err(GitError::InvalidFormat(
817 "fetch refspec is missing a source".into(),
818 ));
819 };
820 if refspec.pattern {
821 for reference in refs {
822 if refspec_is_excluded(&negative, &reference.name)? {
823 continue;
824 }
825 if let Some(dst) = refspec_map_source(refspec, &reference.name)? {
826 updates.push(FetchRefUpdate {
827 src: reference.name.clone(),
828 dst: Some(dst),
829 oid: reference.oid,
830 not_for_merge: false,
831 force: refspec.force,
832 });
833 }
834 }
835 continue;
836 }
837 if refspec_is_excluded(&negative, src)? {
838 continue;
839 }
840 let Some(reference) = find_advertised_ref_by_name_abbrev(refs, src) else {
841 return Err(GitError::reference_not_found(format!("remote ref {src}")));
842 };
843 updates.push(FetchRefUpdate {
844 src: reference.name.clone(),
845 dst: refspec.dst.as_deref().map(fetch_local_ref_name),
846 oid: reference.oid,
847 not_for_merge: false,
848 force: refspec.force,
849 });
850 }
851 if auto_follow_tags && updates.iter().any(|update| update.dst.is_some()) {
852 let fetched_oids = updates.iter().map(|update| update.oid).collect::<Vec<_>>();
853 let fetched_srcs = updates
854 .iter()
855 .map(|update| update.src.clone())
856 .collect::<Vec<_>>();
857 for reference in refs {
858 if reference.name.starts_with("refs/tags/")
859 && fetched_oids.iter().any(|oid| oid == &reference.oid)
860 && !fetched_srcs.contains(&reference.name)
861 && !refspec_is_excluded(&negative, &reference.name)?
862 {
863 updates.push(FetchRefUpdate {
864 src: reference.name.clone(),
865 dst: Some(reference.name.clone()),
866 oid: reference.oid,
867 not_for_merge: true,
868 force: false,
869 });
870 }
871 }
872 }
873 Ok(updates)
874}
875
876pub fn fetch_ref_updates_to_fetch_head(
877 updates: &[FetchRefUpdate],
878 remote: &str,
879) -> Result<Vec<FetchHeadRecord>> {
880 updates
881 .iter()
882 .map(|update| {
883 Ok(FetchHeadRecord {
884 oid: update.oid,
885 not_for_merge: update.not_for_merge,
886 description: fetch_head_remote_description(&update.src, remote)?,
887 })
888 })
889 .collect()
890}
891
892pub fn plan_push_commands(
893 format: ObjectFormat,
894 local_refs: &[PushSourceRef],
895 remote_refs: &[RefAdvertisement],
896 refspecs: &[RefSpec],
897) -> Result<Vec<ReceivePackCommand>> {
898 let zero = zero_object_id(format)?;
899 let mut commands = Vec::new();
900 for refspec in refspecs {
901 validate_refspec_shape(refspec)?;
902 if refspec.negative {
903 return Err(GitError::InvalidFormat(
904 "push refspec must not be negative".into(),
905 ));
906 }
907 match (refspec.src.as_deref(), refspec.dst.as_deref()) {
908 (None, None) => {
909 for local in local_refs {
915 if !local.name.starts_with("refs/") {
916 continue;
917 }
918 validate_push_source_ref(format, local)?;
919 if let Some(remote) = remote_ref(remote_refs, &local.name) {
920 commands.push(ReceivePackCommand {
921 old_id: remote.oid,
922 new_id: local.oid,
923 name: local.name.clone(),
924 });
925 }
926 }
927 }
928 (None, Some(dst)) => {
929 validate_refspec_endpoint("push destination", dst)?;
930 let old_id = remote_ref(remote_refs, dst)
931 .map(|reference| reference.oid)
932 .unwrap_or_else(|| zero.clone());
933 commands.push(ReceivePackCommand {
934 old_id,
935 new_id: zero.clone(),
936 name: dst.to_string(),
937 });
938 }
939 (Some(src), dst) if refspec.pattern => {
940 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
941 return Err(GitError::InvalidFormat(
942 "pattern push refspec source is missing wildcard".into(),
943 ));
944 };
945 let dst = dst.ok_or_else(|| {
946 GitError::InvalidFormat("pattern push refspec is missing destination".into())
947 })?;
948 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
949 GitError::InvalidFormat(
950 "pattern push refspec destination is missing wildcard".into(),
951 )
952 })?;
953 for local in local_refs {
954 validate_push_source_ref(format, local)?;
955 let Some(middle) = local
956 .name
957 .strip_prefix(src_prefix)
958 .and_then(|value| value.strip_suffix(src_suffix))
959 else {
960 continue;
961 };
962 let name = format!("{dst_prefix}{middle}{dst_suffix}");
963 let old_id = remote_ref(remote_refs, &name)
964 .map(|reference| reference.oid)
965 .unwrap_or_else(|| zero.clone());
966 commands.push(ReceivePackCommand {
967 old_id,
968 new_id: local.oid,
969 name,
970 });
971 }
972 }
973 (Some(src), dst) => {
974 validate_refspec_endpoint("push source", src)?;
975 let local = local_ref(local_refs, src)
976 .ok_or_else(|| GitError::reference_not_found(format!("local ref {src}")))?;
977 validate_push_source_ref(format, local)?;
978 let name = dst.unwrap_or(src);
979 validate_refspec_endpoint("push destination", name)?;
980 let old_id = remote_ref(remote_refs, name)
981 .map(|reference| reference.oid)
982 .unwrap_or_else(|| zero.clone());
983 commands.push(ReceivePackCommand {
984 old_id,
985 new_id: local.oid,
986 name: name.to_string(),
987 });
988 }
989 }
990 }
991 Ok(commands)
992}
993
994pub fn build_receive_pack_push_request(
995 features: &ReceivePackFeatures,
996 commands: Vec<ReceivePackCommand>,
997 packfile: Vec<u8>,
998 options: ReceivePackPushRequestOptions,
999) -> Result<ReceivePackPushRequest> {
1000 let mut capabilities = Vec::new();
1001 if options.report_status_v2 {
1002 require_receive_pack_feature(features.report_status_v2, "report-status-v2")?;
1003 capabilities.push(Capability {
1004 name: "report-status-v2".into(),
1005 value: None,
1006 });
1007 } else if options.report_status {
1008 require_receive_pack_feature(features.report_status, "report-status")?;
1009 capabilities.push(Capability {
1010 name: "report-status".into(),
1011 value: None,
1012 });
1013 }
1014 if commands.iter().any(is_receive_pack_delete_command) {
1015 require_receive_pack_feature(features.delete_refs, "delete-refs")?;
1016 capabilities.push(Capability {
1017 name: "delete-refs".into(),
1018 value: None,
1019 });
1020 }
1021 if options.atomic {
1022 require_receive_pack_feature(features.atomic, "atomic")?;
1023 capabilities.push(Capability {
1024 name: "atomic".into(),
1025 value: None,
1026 });
1027 }
1028 if options.ofs_delta {
1029 require_receive_pack_feature(features.ofs_delta, "ofs-delta")?;
1030 capabilities.push(Capability {
1031 name: "ofs-delta".into(),
1032 value: None,
1033 });
1034 }
1035 if options.side_band_64k {
1036 require_receive_pack_feature(features.side_band_64k, "side-band-64k")?;
1037 capabilities.push(Capability {
1038 name: "side-band-64k".into(),
1039 value: None,
1040 });
1041 }
1042 if options.quiet {
1043 require_receive_pack_feature(features.quiet, "quiet")?;
1044 capabilities.push(Capability {
1045 name: "quiet".into(),
1046 value: None,
1047 });
1048 }
1049 if let Some(agent) = &options.agent {
1050 validate_capability_field("receive-pack request agent", agent)?;
1051 capabilities.push(Capability {
1052 name: "agent".into(),
1053 value: Some(agent.clone()),
1054 });
1055 }
1056 if let Some(format) = options.object_format {
1057 if features.object_format != Some(format) {
1058 return Err(GitError::InvalidFormat(
1059 "receive-pack request object-format was not advertised".into(),
1060 ));
1061 }
1062 capabilities.push(Capability {
1063 name: "object-format".into(),
1064 value: Some(format.name().into()),
1065 });
1066 }
1067 let push_options = if options.push_options.is_empty() {
1068 None
1069 } else {
1070 require_receive_pack_feature(features.push_options, "push-options")?;
1071 for option in &options.push_options {
1072 validate_receive_pack_push_option(option.as_bytes())?;
1073 }
1074 capabilities.push(Capability {
1075 name: "push-options".into(),
1076 value: None,
1077 });
1078 Some(options.push_options)
1079 };
1080 let request = ReceivePackPushRequest {
1081 commands: ReceivePackRequest {
1082 commands,
1083 capabilities,
1084 shallow: Vec::new(),
1085 },
1086 push_options,
1087 packfile,
1088 };
1089 validate_receive_pack_push_request_features(features, &request)?;
1090 Ok(request)
1091}
1092
1093pub fn smart_http_info_refs_path(repository_path: &str, service: GitService) -> Result<String> {
1094 validate_smart_http_service(service)?;
1095 let repository_path = normalize_http_repository_path(repository_path)?;
1096 Ok(format!(
1097 "{repository_path}/info/refs?service={}",
1098 service.as_str()
1099 ))
1100}
1101
1102pub fn smart_http_rpc_path(repository_path: &str, service: GitService) -> Result<String> {
1103 validate_smart_http_service(service)?;
1104 let repository_path = normalize_http_repository_path(repository_path)?;
1105 Ok(format!("{repository_path}/{}", service.as_str()))
1106}
1107
1108pub fn dumb_http_info_refs_path(repository_path: &str) -> Result<String> {
1109 let repository_path = normalize_http_repository_path(repository_path)?;
1110 Ok(format!("{repository_path}/info/refs"))
1111}
1112
1113pub fn dumb_http_alternates_path(repository_path: &str) -> Result<String> {
1114 let repository_path = normalize_http_repository_path(repository_path)?;
1115 Ok(format!("{repository_path}/objects/info/http-alternates"))
1116}
1117
1118pub fn dumb_http_packs_path(repository_path: &str) -> Result<String> {
1119 let repository_path = normalize_http_repository_path(repository_path)?;
1120 Ok(format!("{repository_path}/objects/info/packs"))
1121}
1122
1123pub fn dumb_http_loose_object_path(repository_path: &str, oid: &ObjectId) -> Result<String> {
1124 let repository_path = normalize_http_repository_path(repository_path)?;
1125 let oid = oid.to_string();
1126 let (directory, file) = oid.split_at(2);
1127 Ok(format!("{repository_path}/objects/{directory}/{file}"))
1128}
1129
1130pub fn dumb_http_pack_file_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
1131 dumb_http_pack_resource_path(repository_path, hash, "pack")
1132}
1133
1134pub fn dumb_http_pack_index_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
1135 dumb_http_pack_resource_path(repository_path, hash, "idx")
1136}
1137
1138pub fn smart_http_advertisement_content_type(service: GitService) -> Result<String> {
1139 validate_smart_http_service(service)?;
1140 Ok(format!("application/x-{}-advertisement", service.as_str()))
1141}
1142
1143pub fn smart_http_rpc_request_content_type(service: GitService) -> Result<String> {
1144 validate_smart_http_service(service)?;
1145 Ok(format!("application/x-{}-request", service.as_str()))
1146}
1147
1148pub fn smart_http_rpc_result_content_type(service: GitService) -> Result<String> {
1149 validate_smart_http_service(service)?;
1150 Ok(format!("application/x-{}-result", service.as_str()))
1151}
1152
1153pub fn parse_smart_http_advertisement_content_type(value: &str) -> Result<GitService> {
1154 parse_smart_http_content_type(value, "-advertisement")
1155}
1156
1157pub fn parse_smart_http_rpc_request_content_type(value: &str) -> Result<GitService> {
1158 parse_smart_http_content_type(value, "-request")
1159}
1160
1161pub fn parse_smart_http_rpc_result_content_type(value: &str) -> Result<GitService> {
1162 parse_smart_http_content_type(value, "-result")
1163}
1164
1165pub fn parse_sideband_packet(payload: &[u8]) -> Result<SideBandPacket> {
1166 let Some((&channel, data)) = payload.split_first() else {
1167 return Err(GitError::InvalidFormat("sideband packet is empty".into()));
1168 };
1169 let channel = match channel {
1170 1 => SideBandChannel::Data,
1171 2 => SideBandChannel::Progress,
1172 3 => SideBandChannel::Fatal,
1173 other => {
1174 return Err(GitError::InvalidFormat(format!(
1175 "invalid sideband channel {other}"
1176 )));
1177 }
1178 };
1179 Ok(SideBandPacket {
1180 channel,
1181 data: data.to_vec(),
1182 })
1183}
1184
1185pub fn encode_sideband_packet(packet: &SideBandPacket) -> Result<Vec<u8>> {
1186 let mut out = Vec::with_capacity(packet.data.len() + 1);
1187 out.push(match packet.channel {
1188 SideBandChannel::Data => 1,
1189 SideBandChannel::Progress => 2,
1190 SideBandChannel::Fatal => 3,
1191 });
1192 out.extend_from_slice(&packet.data);
1193 if out.len() > PKT_LINE_MAX_PAYLOAD_LEN {
1194 return Err(GitError::InvalidFormat(format!(
1195 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1196 )));
1197 }
1198 Ok(out)
1199}
1200
1201pub fn write_sideband_packet(writer: &mut impl Write, packet: &SideBandPacket) -> Result<()> {
1202 write_sideband_payload(writer, packet.channel, &packet.data)
1203}
1204
1205fn write_sideband_payload(
1206 writer: &mut impl Write,
1207 channel: SideBandChannel,
1208 data: &[u8],
1209) -> Result<()> {
1210 let payload_len = data
1211 .len()
1212 .checked_add(1)
1213 .ok_or_else(|| GitError::InvalidFormat("sideband packet length overflow".into()))?;
1214 if payload_len > PKT_LINE_MAX_PAYLOAD_LEN {
1215 return Err(GitError::InvalidFormat(format!(
1216 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1217 )));
1218 }
1219 writer.write_all(&pkt_line_header(payload_len + 4))?;
1220 writer.write_all(&[match channel {
1221 SideBandChannel::Data => 1,
1222 SideBandChannel::Progress => 2,
1223 SideBandChannel::Fatal => 3,
1224 }])?;
1225 writer.write_all(data)?;
1226 Ok(())
1227}
1228
1229pub fn parse_sideband_packets(payloads: &[Vec<u8>]) -> Result<Vec<SideBandPacket>> {
1230 payloads
1231 .iter()
1232 .map(|payload| parse_sideband_packet(payload))
1233 .collect()
1234}
1235
1236pub fn encode_sideband_packets(packets: &[SideBandPacket]) -> Result<Vec<Vec<u8>>> {
1237 packets.iter().map(encode_sideband_packet).collect()
1238}
1239
1240pub fn parse_sideband_stream(frames: &[PktLineFrame]) -> Result<Vec<SideBandPacket>> {
1241 let mut packets = Vec::new();
1242 let mut saw_flush = false;
1243 for (idx, frame) in frames.iter().enumerate() {
1244 match frame {
1245 PktLineFrame::Data(payload) if !saw_flush => {
1246 packets.push(parse_sideband_packet(payload)?);
1247 }
1248 PktLineFrame::Data(_) => {
1249 return Err(GitError::InvalidFormat(
1250 "sideband stream has data after flush".into(),
1251 ));
1252 }
1253 PktLineFrame::Flush => {
1254 saw_flush = true;
1255 if idx + 1 != frames.len() {
1256 return Err(GitError::InvalidFormat(
1257 "sideband stream has frames after flush".into(),
1258 ));
1259 }
1260 }
1261 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1262 return Err(GitError::InvalidFormat(
1263 "sideband stream contains a non-flush control packet".into(),
1264 ));
1265 }
1266 }
1267 }
1268 if !saw_flush {
1269 return Err(GitError::InvalidFormat(
1270 "sideband stream missing flush".into(),
1271 ));
1272 }
1273 Ok(packets)
1274}
1275
1276pub fn encode_sideband_stream(packets: &[SideBandPacket]) -> Result<Vec<PktLineFrame>> {
1277 let mut frames = Vec::new();
1278 for packet in packets {
1279 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
1280 }
1281 frames.push(PktLineFrame::Flush);
1282 Ok(frames)
1283}
1284
1285pub fn read_sideband_stream(reader: &mut impl Read) -> Result<Vec<SideBandPacket>> {
1286 let frames = read_pkt_line_frames_until_flush(reader)?;
1287 parse_sideband_stream(&frames)
1288}
1289
1290pub fn write_sideband_stream(writer: &mut impl Write, packets: &[SideBandPacket]) -> Result<()> {
1291 for packet in packets {
1292 write_sideband_packet(writer, packet)?;
1293 }
1294 writer.write_all(b"0000")?;
1295 Ok(())
1296}
1297
1298pub fn demux_sideband_packets(packets: &[SideBandPacket]) -> Result<SideBandDemux> {
1299 let mut out = SideBandDemux::default();
1300 for packet in packets {
1301 match packet.channel {
1302 SideBandChannel::Data => out.data.extend_from_slice(&packet.data),
1303 SideBandChannel::Progress => out.progress.push(packet.data.clone()),
1304 SideBandChannel::Fatal => {
1305 let message = String::from_utf8_lossy(&packet.data).into_owned();
1306 return Err(GitError::InvalidFormat(format!(
1307 "sideband fatal: {message}"
1308 )));
1309 }
1310 }
1311 }
1312 Ok(out)
1313}
1314
1315pub fn parse_and_demux_sideband_packets(payloads: &[Vec<u8>]) -> Result<SideBandDemux> {
1316 let packets = parse_sideband_packets(payloads)?;
1317 demux_sideband_packets(&packets)
1318}
1319
1320pub fn demux_sideband_stream(frames: &[PktLineFrame]) -> Result<SideBandDemux> {
1321 let packets = parse_sideband_stream(frames)?;
1322 demux_sideband_packets(&packets)
1323}
1324
1325pub fn read_and_demux_sideband_stream(reader: &mut impl Read) -> Result<SideBandDemux> {
1326 let packets = read_sideband_stream(reader)?;
1327 demux_sideband_packets(&packets)
1328}
1329
1330pub fn parse_upload_archive_request(frames: &[PktLineFrame]) -> Result<UploadArchiveRequest> {
1331 let mut request = UploadArchiveRequest::default();
1332 let mut saw_flush = false;
1333 for (idx, frame) in frames.iter().enumerate() {
1334 match frame {
1335 PktLineFrame::Data(payload) if !saw_flush => {
1336 let text = parse_protocol_v2_line_text("upload-archive request argument", payload)?;
1337 let argument = text.strip_prefix("argument ").ok_or_else(|| {
1338 GitError::InvalidFormat("upload-archive request line must be argument".into())
1339 })?;
1340 validate_upload_archive_argument(argument)?;
1341 request.arguments.push(argument.to_string());
1342 }
1343 PktLineFrame::Data(_) => {
1344 return Err(GitError::InvalidFormat(
1345 "upload-archive request has data after flush".into(),
1346 ));
1347 }
1348 PktLineFrame::Flush => {
1349 saw_flush = true;
1350 if idx + 1 != frames.len() {
1351 return Err(GitError::InvalidFormat(
1352 "upload-archive request has frames after flush".into(),
1353 ));
1354 }
1355 }
1356 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1357 return Err(GitError::InvalidFormat(
1358 "upload-archive request contains a non-flush control packet".into(),
1359 ));
1360 }
1361 }
1362 }
1363 if !saw_flush {
1364 return Err(GitError::InvalidFormat(
1365 "upload-archive request missing flush".into(),
1366 ));
1367 }
1368 if request.arguments.is_empty() {
1369 return Err(GitError::InvalidFormat(
1370 "upload-archive request is missing arguments".into(),
1371 ));
1372 }
1373 Ok(request)
1374}
1375
1376pub fn encode_upload_archive_request(request: &UploadArchiveRequest) -> Result<Vec<PktLineFrame>> {
1377 if request.arguments.is_empty() {
1378 return Err(GitError::InvalidFormat(
1379 "upload-archive request is missing arguments".into(),
1380 ));
1381 }
1382 let mut frames = Vec::new();
1383 for argument in &request.arguments {
1384 validate_upload_archive_argument(argument)?;
1385 frames.push(PktLineFrame::data(line_from_str(&format!(
1386 "argument {argument}"
1387 )))?);
1388 }
1389 frames.push(PktLineFrame::Flush);
1390 Ok(frames)
1391}
1392
1393pub fn read_upload_archive_request(reader: &mut impl Read) -> Result<UploadArchiveRequest> {
1394 let frames = read_pkt_line_frames_until_flush(reader)?;
1395 parse_upload_archive_request(&frames)
1396}
1397
1398pub fn write_upload_archive_request(
1399 writer: &mut impl Write,
1400 request: &UploadArchiveRequest,
1401) -> Result<()> {
1402 if request.arguments.is_empty() {
1403 return Err(GitError::InvalidFormat(
1404 "upload-archive request is missing arguments".into(),
1405 ));
1406 }
1407 for argument in &request.arguments {
1408 validate_upload_archive_argument(argument)?;
1409 write_pkt_line_payload(writer, &line_from_str(&format!("argument {argument}")))?;
1410 }
1411 writer.write_all(b"0000")?;
1412 Ok(())
1413}
1414
1415pub fn parse_upload_archive_response(frames: &[PktLineFrame]) -> Result<UploadArchiveResponse> {
1416 let Some((first, rest)) = frames.split_first() else {
1417 return Err(GitError::InvalidFormat(
1418 "upload-archive response is empty".into(),
1419 ));
1420 };
1421 let PktLineFrame::Data(payload) = first else {
1422 return Err(GitError::InvalidFormat(
1423 "upload-archive response must start with a data packet".into(),
1424 ));
1425 };
1426 let text = parse_protocol_v2_line_text("upload-archive response status", payload)?;
1427 if text == "ACK" {
1428 return Ok(UploadArchiveResponse::Ack {
1429 sideband: parse_sideband_stream(rest)?,
1430 });
1431 }
1432 if let Some(message) = text.strip_prefix("NACK ") {
1433 validate_upload_archive_status_message(message)?;
1434 if !matches!(rest, [PktLineFrame::Flush]) {
1435 return Err(GitError::InvalidFormat(
1436 "upload-archive NACK response must end with flush".into(),
1437 ));
1438 }
1439 return Ok(UploadArchiveResponse::Nack {
1440 message: message.to_string(),
1441 });
1442 }
1443 Err(GitError::InvalidFormat(format!(
1444 "unsupported upload-archive response status {text}"
1445 )))
1446}
1447
1448pub fn encode_upload_archive_response(
1449 response: &UploadArchiveResponse,
1450) -> Result<Vec<PktLineFrame>> {
1451 let mut frames = Vec::new();
1452 match response {
1453 UploadArchiveResponse::Ack { sideband } => {
1454 frames.push(PktLineFrame::data(line_from_str("ACK"))?);
1455 frames.extend(encode_sideband_stream(sideband)?);
1456 }
1457 UploadArchiveResponse::Nack { message } => {
1458 validate_upload_archive_status_message(message)?;
1459 frames.push(PktLineFrame::data(line_from_str(&format!(
1460 "NACK {message}"
1461 )))?);
1462 frames.push(PktLineFrame::Flush);
1463 }
1464 }
1465 Ok(frames)
1466}
1467
1468pub fn read_upload_archive_response(reader: &mut impl Read) -> Result<UploadArchiveResponse> {
1469 let frames = read_pkt_line_frames_until_flush(reader)?;
1470 parse_upload_archive_response(&frames)
1471}
1472
1473pub fn write_upload_archive_response(
1474 writer: &mut impl Write,
1475 response: &UploadArchiveResponse,
1476) -> Result<()> {
1477 match response {
1478 UploadArchiveResponse::Ack { sideband } => {
1479 write_pkt_line_payload(writer, b"ACK\n")?;
1480 write_sideband_stream(writer, sideband)?;
1481 }
1482 UploadArchiveResponse::Nack { message } => {
1483 validate_upload_archive_status_message(message)?;
1484 write_pkt_line_payload(writer, &line_from_str(&format!("NACK {message}")))?;
1485 writer.write_all(b"0000")?;
1486 }
1487 }
1488 Ok(())
1489}
1490
1491pub fn demux_upload_archive_response(response: &UploadArchiveResponse) -> Result<SideBandDemux> {
1492 match response {
1493 UploadArchiveResponse::Ack { sideband } => demux_sideband_packets(sideband),
1494 UploadArchiveResponse::Nack { message } => Err(GitError::InvalidFormat(format!(
1495 "upload-archive NACK: {message}"
1496 ))),
1497 }
1498}
1499
1500fn parse_pkt_len(bytes: &[u8]) -> Result<usize> {
1501 let mut len = 0usize;
1502 for byte in bytes {
1503 len = (len << 4) | hex_nibble(*byte)? as usize;
1504 }
1505 Ok(len)
1506}
1507
1508fn hex_nibble(byte: u8) -> Result<u8> {
1509 match byte {
1510 b'0'..=b'9' => Ok(byte - b'0'),
1511 b'a'..=b'f' => Ok(byte - b'a' + 10),
1512 b'A'..=b'F' => Ok(byte - b'A' + 10),
1513 _ => Err(GitError::InvalidFormat(format!(
1514 "invalid pkt-line length byte {byte:#04x}"
1515 ))),
1516 }
1517}
1518
1519#[derive(Debug, Clone, PartialEq, Eq)]
1520pub struct TransportHandshake {
1521 pub protocol: ProtocolVersion,
1522 pub capabilities: Vec<Capability>,
1523}
1524
1525#[derive(Debug, Clone, PartialEq, Eq)]
1526pub struct RefAdvertisement {
1527 pub oid: ObjectId,
1528 pub name: String,
1529 pub capabilities: Vec<Capability>,
1530}
1531
1532#[derive(Debug, Clone, PartialEq, Eq)]
1533pub struct DumbHttpRefRecord {
1534 pub oid: ObjectId,
1535 pub name: String,
1536 pub peeled: bool,
1537}
1538
1539#[derive(Debug, Clone, PartialEq, Eq)]
1540pub struct DumbHttpPackRecord {
1541 pub hash: ObjectId,
1542}
1543
1544#[derive(Debug, Clone, PartialEq, Eq)]
1545pub struct RefAdvertisementSet {
1546 pub protocol: ProtocolVersion,
1547 pub refs: Vec<RefAdvertisement>,
1548 pub shallow: Vec<ObjectId>,
1549}
1550
1551#[derive(Debug, Clone, PartialEq, Eq, Default)]
1552pub struct UploadPackRequest {
1553 pub wants: Vec<ObjectId>,
1554 pub capabilities: Vec<Capability>,
1555 pub shallow: Vec<ObjectId>,
1556 pub deepen: Option<u32>,
1557 pub deepen_since: Option<u64>,
1558 pub deepen_not: Vec<String>,
1559 pub filter: Option<String>,
1560}
1561
1562#[derive(Debug, Clone, PartialEq, Eq, Default)]
1563pub struct UploadPackFeatures {
1564 pub multi_ack: bool,
1565 pub multi_ack_detailed: bool,
1566 pub no_done: bool,
1567 pub thin_pack: bool,
1568 pub side_band: bool,
1569 pub side_band_64k: bool,
1570 pub ofs_delta: bool,
1571 pub shallow: bool,
1572 pub deepen_since: bool,
1573 pub deepen_not: bool,
1574 pub include_tag: bool,
1575 pub no_progress: bool,
1576 pub allow_tip_sha1_in_want: bool,
1577 pub allow_reachable_sha1_in_want: bool,
1578 pub filter: bool,
1579 pub agent: Option<String>,
1580 pub object_format: Option<ObjectFormat>,
1581 pub symrefs: Vec<String>,
1582 pub unknown: Vec<Capability>,
1583}
1584
1585#[derive(Debug, Clone, PartialEq, Eq, Default)]
1586pub struct UploadPackNegotiationRequest {
1587 pub haves: Vec<ObjectId>,
1588 pub done: bool,
1589}
1590
1591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1592pub enum UploadPackAckStatus {
1593 Continue,
1594 Common,
1595 Ready,
1596}
1597
1598#[derive(Debug, Clone, PartialEq, Eq)]
1599pub enum UploadPackAcknowledgment {
1600 Nak,
1601 Ack {
1602 oid: ObjectId,
1603 status: Option<UploadPackAckStatus>,
1604 },
1605}
1606
1607#[derive(Debug, Clone, PartialEq, Eq, Default)]
1608pub struct UploadPackPackfileResponse {
1609 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1610 pub sideband: Vec<SideBandPacket>,
1611}
1612
1613#[derive(Debug, Clone, PartialEq, Eq, Default)]
1614pub struct UploadPackRawPackfileResponse {
1615 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1616 pub packfile: Vec<u8>,
1617}
1618
1619#[derive(Debug, Clone, PartialEq, Eq)]
1620pub struct ReceivePackCommand {
1621 pub old_id: ObjectId,
1622 pub new_id: ObjectId,
1623 pub name: String,
1624}
1625
1626#[derive(Debug, Clone, PartialEq, Eq, Default)]
1627pub struct ReceivePackRequest {
1628 pub shallow: Vec<ObjectId>,
1629 pub commands: Vec<ReceivePackCommand>,
1630 pub capabilities: Vec<Capability>,
1631}
1632
1633#[derive(Debug, Clone, PartialEq, Eq, Default)]
1634pub struct ReceivePackPushRequest {
1635 pub commands: ReceivePackRequest,
1636 pub push_options: Option<Vec<String>>,
1637 pub packfile: Vec<u8>,
1638}
1639
1640#[derive(Debug, Clone, PartialEq, Eq, Default)]
1641pub struct ReceivePackPushRequestOptions {
1642 pub report_status: bool,
1643 pub report_status_v2: bool,
1644 pub atomic: bool,
1645 pub ofs_delta: bool,
1646 pub side_band_64k: bool,
1647 pub quiet: bool,
1648 pub agent: Option<String>,
1649 pub object_format: Option<ObjectFormat>,
1650 pub push_options: Vec<String>,
1651}
1652
1653#[derive(Debug, Clone, PartialEq, Eq, Default)]
1654pub struct ReceivePackFeatures {
1655 pub report_status: bool,
1656 pub report_status_v2: bool,
1657 pub delete_refs: bool,
1658 pub ofs_delta: bool,
1659 pub atomic: bool,
1660 pub push_options: bool,
1661 pub side_band_64k: bool,
1662 pub quiet: bool,
1663 pub no_thin: bool,
1664 pub agent: Option<String>,
1665 pub object_format: Option<ObjectFormat>,
1666 pub unknown: Vec<Capability>,
1667}
1668
1669#[derive(Debug, Clone, PartialEq, Eq)]
1670pub enum ReceivePackUnpackStatus {
1671 Ok,
1672 Error(String),
1673}
1674
1675#[derive(Debug, Clone, PartialEq, Eq)]
1676pub enum ReceivePackCommandStatus {
1677 Ok { name: String },
1678 Ng { name: String, message: String },
1679}
1680
1681#[derive(Debug, Clone, PartialEq, Eq)]
1682pub struct ReceivePackReportStatus {
1683 pub unpack: ReceivePackUnpackStatus,
1684 pub commands: Vec<ReceivePackCommandStatus>,
1685}
1686
1687#[derive(Debug, Clone, PartialEq, Eq, Default)]
1688pub struct ReceivePackCommandStatusV2Options {
1689 pub refname: Option<String>,
1690 pub old_oid: Option<ObjectId>,
1691 pub new_oid: Option<ObjectId>,
1692 pub forced_update: bool,
1693}
1694
1695#[derive(Debug, Clone, PartialEq, Eq)]
1696pub enum ReceivePackCommandStatusV2 {
1697 Ok {
1698 name: String,
1699 options: ReceivePackCommandStatusV2Options,
1700 },
1701 Ng {
1702 name: String,
1703 message: String,
1704 },
1705}
1706
1707#[derive(Debug, Clone, PartialEq, Eq)]
1708pub struct ReceivePackReportStatusV2 {
1709 pub unpack: ReceivePackUnpackStatus,
1710 pub commands: Vec<ReceivePackCommandStatusV2>,
1711}
1712
1713#[derive(Debug, Clone, PartialEq, Eq)]
1714pub struct ProtocolV2CommandRequest {
1715 pub command: String,
1716 pub capabilities: Vec<Capability>,
1717 pub arguments: Vec<Vec<u8>>,
1718}
1719
1720#[derive(Debug, Clone, PartialEq, Eq)]
1721pub enum ProtocolV2Request {
1722 Command(ProtocolV2CommandRequest),
1723 Done,
1724}
1725
1726#[derive(Debug, Clone, PartialEq, Eq)]
1727pub enum ProtocolV2Command {
1728 LsRefs(ProtocolV2LsRefsRequest),
1729 Fetch(ProtocolV2FetchRequest),
1730 ObjectInfo(ProtocolV2ObjectInfoRequest),
1731 Unknown(ProtocolV2CommandRequest),
1732}
1733
1734#[derive(Debug, Clone, PartialEq, Eq)]
1735pub enum ProtocolV2SessionRequest {
1736 Command(ProtocolV2Command),
1737 Done,
1738}
1739
1740#[derive(Debug, Clone, PartialEq, Eq, Default)]
1741pub struct ProtocolV2CommandOptions {
1742 pub agent: Option<String>,
1743 pub object_format: Option<ObjectFormat>,
1744 pub server_options: Vec<String>,
1745 pub extra: Vec<Capability>,
1746}
1747
1748#[derive(Debug, Clone, PartialEq, Eq, Default)]
1749pub struct ProtocolV2FetchFeatures {
1750 pub shallow: bool,
1751 pub wait_for_done: bool,
1752 pub filter: bool,
1753 pub ref_in_want: bool,
1754 pub sideband_all: bool,
1755 pub packfile_uris: bool,
1756 pub unknown: Vec<String>,
1757}
1758
1759#[derive(Debug, Clone, PartialEq, Eq, Default)]
1760pub struct ProtocolV2LsRefsFeatures {
1761 pub unborn: bool,
1762 pub unknown: Vec<String>,
1763}
1764
1765impl ProtocolV2CommandRequest {
1766 pub fn new(command: impl Into<String>) -> Result<Self> {
1767 let command = command.into();
1768 validate_capability_name(&command)?;
1769 Ok(Self {
1770 command,
1771 capabilities: Vec::new(),
1772 arguments: Vec::new(),
1773 })
1774 }
1775}
1776
1777#[derive(Debug, Clone, PartialEq, Eq, Default)]
1778pub struct ProtocolV2LsRefsRequest {
1779 pub peel: bool,
1780 pub symrefs: bool,
1781 pub unborn: bool,
1782 pub ref_prefixes: Vec<String>,
1783}
1784
1785#[derive(Debug, Clone, PartialEq, Eq)]
1786pub struct ProtocolV2LsRefsRef {
1787 pub oid: ObjectId,
1788 pub name: String,
1789 pub peeled: Option<ObjectId>,
1790 pub symref_target: Option<String>,
1791 pub attributes: Vec<String>,
1792}
1793
1794#[derive(Debug, Clone, PartialEq, Eq)]
1795pub enum ProtocolV2LsRefsRecord {
1796 Ref(ProtocolV2LsRefsRef),
1797 Unborn {
1798 name: String,
1799 symref_target: Option<String>,
1800 attributes: Vec<String>,
1801 },
1802}
1803
1804#[derive(Debug, Clone, PartialEq, Eq, Default)]
1805pub struct ProtocolV2FetchRequest {
1806 pub wants: Vec<ObjectId>,
1807 pub want_refs: Vec<String>,
1808 pub haves: Vec<ObjectId>,
1809 pub shallow: Vec<ObjectId>,
1810 pub deepen: Option<u32>,
1811 pub deepen_since: Option<u64>,
1812 pub deepen_not: Vec<String>,
1813 pub deepen_relative: bool,
1814 pub filter: Option<String>,
1815 pub packfile_uris: Option<String>,
1816 pub thin_pack: bool,
1817 pub no_progress: bool,
1818 pub include_tag: bool,
1819 pub ofs_delta: bool,
1820 pub sideband_all: bool,
1821 pub wait_for_done: bool,
1822 pub done: bool,
1823}
1824
1825#[derive(Debug, Clone, PartialEq, Eq)]
1826pub enum ProtocolV2FetchAcknowledgment {
1827 Nak,
1828 Ack(ObjectId),
1829 Ready,
1830}
1831
1832#[derive(Debug, Clone, PartialEq, Eq)]
1833pub enum ProtocolV2FetchShallowInfo {
1834 Shallow(ObjectId),
1835 Unshallow(ObjectId),
1836}
1837
1838#[derive(Debug, Clone, PartialEq, Eq)]
1839pub struct ProtocolV2FetchWantedRef {
1840 pub oid: ObjectId,
1841 pub name: String,
1842}
1843
1844#[derive(Debug, Clone, PartialEq, Eq)]
1845pub struct ProtocolV2FetchPackfileUri {
1846 pub pack_hash: ObjectId,
1847 pub uri: String,
1848}
1849
1850#[derive(Debug, Clone, PartialEq, Eq)]
1851pub enum ProtocolV2FetchResponseSection {
1852 Acknowledgments(Vec<ProtocolV2FetchAcknowledgment>),
1853 ShallowInfo(Vec<ProtocolV2FetchShallowInfo>),
1854 WantedRefs(Vec<ProtocolV2FetchWantedRef>),
1855 PackfileUris(Vec<ProtocolV2FetchPackfileUri>),
1856 Packfile(Vec<Vec<u8>>),
1857 Unknown { name: String, lines: Vec<Vec<u8>> },
1858}
1859
1860#[derive(Debug, Clone, PartialEq, Eq, Default)]
1861pub struct ProtocolV2FetchSidebandAllResponse {
1862 pub sections: Vec<ProtocolV2FetchResponseSection>,
1863 pub progress: Vec<Vec<u8>>,
1864}
1865
1866#[derive(Debug, Clone, PartialEq, Eq, Default)]
1867pub struct ProtocolV2ObjectInfoRequest {
1868 pub size: bool,
1869 pub oids: Vec<ObjectId>,
1870}
1871
1872#[derive(Debug, Clone, PartialEq, Eq)]
1873pub struct ProtocolV2ObjectInfoRecord {
1874 pub oid: ObjectId,
1875 pub size: u64,
1876}
1877
1878#[derive(Debug, Clone, PartialEq, Eq, Default)]
1879pub struct ProtocolV2ObjectInfoResponse {
1880 pub size: bool,
1881 pub records: Vec<ProtocolV2ObjectInfoRecord>,
1882}
1883
1884impl ProtocolV2LsRefsRequest {
1885 pub fn from_command_request(request: &ProtocolV2CommandRequest) -> Result<Self> {
1886 if request.command != "ls-refs" {
1887 return Err(GitError::InvalidFormat(format!(
1888 "expected ls-refs command, got {}",
1889 request.command
1890 )));
1891 }
1892 let mut out = Self::default();
1893 for argument in &request.arguments {
1894 let text = std::str::from_utf8(argument)
1895 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1896 match text {
1897 "peel" => out.peel = true,
1898 "symrefs" => out.symrefs = true,
1899 "unborn" => out.unborn = true,
1900 value if value.starts_with("ref-prefix ") => {
1901 let prefix = value
1902 .strip_prefix("ref-prefix ")
1903 .ok_or_else(|| GitError::InvalidFormat("invalid ref-prefix".into()))?;
1904 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1905 out.ref_prefixes.push(prefix.to_string());
1906 }
1907 other => {
1908 return Err(GitError::InvalidFormat(format!(
1909 "unsupported ls-refs argument {other}"
1910 )));
1911 }
1912 }
1913 }
1914 Ok(out)
1915 }
1916
1917 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1918 let mut request = ProtocolV2CommandRequest::new("ls-refs")?;
1919 if self.peel {
1920 request.arguments.push(b"peel".to_vec());
1921 }
1922 if self.symrefs {
1923 request.arguments.push(b"symrefs".to_vec());
1924 }
1925 if self.unborn {
1926 request.arguments.push(b"unborn".to_vec());
1927 }
1928 for prefix in &self.ref_prefixes {
1929 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1930 request
1931 .arguments
1932 .push(format!("ref-prefix {prefix}").into_bytes());
1933 }
1934 Ok(request)
1935 }
1936}
1937
1938impl ProtocolV2FetchRequest {
1939 pub fn from_command_request(
1940 format: ObjectFormat,
1941 request: &ProtocolV2CommandRequest,
1942 ) -> Result<Self> {
1943 if request.command != "fetch" {
1944 return Err(GitError::InvalidFormat(format!(
1945 "expected fetch command, got {}",
1946 request.command
1947 )));
1948 }
1949 let mut out = Self::default();
1950 for argument in &request.arguments {
1951 let text = std::str::from_utf8(argument)
1952 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1953 match text {
1954 "thin-pack" => out.thin_pack = true,
1955 "no-progress" => out.no_progress = true,
1956 "include-tag" => out.include_tag = true,
1957 "ofs-delta" => out.ofs_delta = true,
1958 "sideband-all" => out.sideband_all = true,
1959 "wait-for-done" => out.wait_for_done = true,
1960 "deepen-relative" => out.deepen_relative = true,
1961 "done" => out.done = true,
1962 value if value.starts_with("want ") => {
1963 out.wants
1964 .push(parse_oid_argument(format, "fetch want", value, "want ")?);
1965 }
1966 value if value.starts_with("want-ref ") => {
1967 let name = value
1968 .strip_prefix("want-ref ")
1969 .ok_or_else(|| GitError::InvalidFormat("invalid fetch want-ref".into()))?;
1970 validate_protocol_v2_token("fetch want-ref", name)?;
1971 out.want_refs.push(name.to_string());
1972 }
1973 value if value.starts_with("have ") => {
1974 out.haves
1975 .push(parse_oid_argument(format, "fetch have", value, "have ")?);
1976 }
1977 value if value.starts_with("shallow ") => {
1978 out.shallow.push(parse_oid_argument(
1979 format,
1980 "fetch shallow",
1981 value,
1982 "shallow ",
1983 )?);
1984 }
1985 value if value.starts_with("deepen ") => {
1986 if out.deepen.is_some() {
1987 return Err(GitError::InvalidFormat(
1988 "fetch request has duplicate deepen".into(),
1989 ));
1990 }
1991 out.deepen = Some(parse_u32_argument("fetch deepen", value, "deepen ")?);
1992 }
1993 value if value.starts_with("deepen-since ") => {
1994 if out.deepen_since.is_some() {
1995 return Err(GitError::InvalidFormat(
1996 "fetch request has duplicate deepen-since".into(),
1997 ));
1998 }
1999 out.deepen_since = Some(parse_u64_argument(
2000 "fetch deepen-since",
2001 value,
2002 "deepen-since ",
2003 )?);
2004 }
2005 value if value.starts_with("deepen-not ") => {
2006 let name = value.strip_prefix("deepen-not ").ok_or_else(|| {
2007 GitError::InvalidFormat("invalid fetch deepen-not".into())
2008 })?;
2009 validate_protocol_v2_token("fetch deepen-not", name)?;
2010 out.deepen_not.push(name.to_string());
2011 }
2012 value if value.starts_with("filter ") => {
2013 if out.filter.is_some() {
2014 return Err(GitError::InvalidFormat(
2015 "fetch request has duplicate filter".into(),
2016 ));
2017 }
2018 let filter = value
2019 .strip_prefix("filter ")
2020 .ok_or_else(|| GitError::InvalidFormat("invalid fetch filter".into()))?;
2021 validate_protocol_v2_token("fetch filter", filter)?;
2022 out.filter = Some(filter.to_string());
2023 }
2024 value if value.starts_with("packfile-uris ") => {
2025 if out.packfile_uris.is_some() {
2026 return Err(GitError::InvalidFormat(
2027 "fetch request has duplicate packfile-uris".into(),
2028 ));
2029 }
2030 let protocols = value.strip_prefix("packfile-uris ").ok_or_else(|| {
2031 GitError::InvalidFormat("invalid fetch packfile-uris".into())
2032 })?;
2033 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
2034 out.packfile_uris = Some(protocols.to_string());
2035 }
2036 other => {
2037 return Err(GitError::InvalidFormat(format!(
2038 "unsupported fetch argument {other}"
2039 )));
2040 }
2041 }
2042 }
2043 Ok(out)
2044 }
2045
2046 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
2047 let mut request = ProtocolV2CommandRequest::new("fetch")?;
2048 for oid in &self.wants {
2049 request.arguments.push(format!("want {oid}").into_bytes());
2050 }
2051 for name in &self.want_refs {
2052 validate_protocol_v2_token("fetch want-ref", name)?;
2053 request
2054 .arguments
2055 .push(format!("want-ref {name}").into_bytes());
2056 }
2057 for oid in &self.haves {
2058 request.arguments.push(format!("have {oid}").into_bytes());
2059 }
2060 for oid in &self.shallow {
2061 request
2062 .arguments
2063 .push(format!("shallow {oid}").into_bytes());
2064 }
2065 if let Some(deepen) = self.deepen {
2066 if deepen == 0 {
2067 return Err(GitError::InvalidFormat(
2068 "fetch deepen must be positive".into(),
2069 ));
2070 }
2071 request
2072 .arguments
2073 .push(format!("deepen {deepen}").into_bytes());
2074 }
2075 if let Some(deepen_since) = self.deepen_since {
2076 request
2077 .arguments
2078 .push(format!("deepen-since {deepen_since}").into_bytes());
2079 }
2080 for name in &self.deepen_not {
2081 validate_protocol_v2_token("fetch deepen-not", name)?;
2082 request
2083 .arguments
2084 .push(format!("deepen-not {name}").into_bytes());
2085 }
2086 if self.deepen_relative {
2087 request.arguments.push(b"deepen-relative".to_vec());
2088 }
2089 if let Some(filter) = &self.filter {
2090 validate_protocol_v2_token("fetch filter", filter)?;
2091 request
2092 .arguments
2093 .push(format!("filter {filter}").into_bytes());
2094 }
2095 if let Some(protocols) = &self.packfile_uris {
2096 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
2097 request
2098 .arguments
2099 .push(format!("packfile-uris {protocols}").into_bytes());
2100 }
2101 if self.thin_pack {
2102 request.arguments.push(b"thin-pack".to_vec());
2103 }
2104 if self.no_progress {
2105 request.arguments.push(b"no-progress".to_vec());
2106 }
2107 if self.include_tag {
2108 request.arguments.push(b"include-tag".to_vec());
2109 }
2110 if self.ofs_delta {
2111 request.arguments.push(b"ofs-delta".to_vec());
2112 }
2113 if self.sideband_all {
2114 request.arguments.push(b"sideband-all".to_vec());
2115 }
2116 if self.wait_for_done {
2117 request.arguments.push(b"wait-for-done".to_vec());
2118 }
2119 if self.done {
2120 request.arguments.push(b"done".to_vec());
2121 }
2122 Ok(request)
2123 }
2124}
2125
2126impl ProtocolV2ObjectInfoRequest {
2127 pub fn from_command_request(
2128 format: ObjectFormat,
2129 request: &ProtocolV2CommandRequest,
2130 ) -> Result<Self> {
2131 if request.command != "object-info" {
2132 return Err(GitError::InvalidFormat(format!(
2133 "expected object-info command, got {}",
2134 request.command
2135 )));
2136 }
2137 let mut out = Self::default();
2138 for argument in &request.arguments {
2139 let text = parse_protocol_v2_line_text("object-info request argument", argument)?;
2140 if text == "size" {
2141 if out.size {
2142 return Err(GitError::InvalidFormat(
2143 "object-info request has duplicate size argument".into(),
2144 ));
2145 }
2146 out.size = true;
2147 } else if text.starts_with("oid ") {
2148 out.oids
2149 .push(parse_oid_argument(format, "object-info oid", text, "oid ")?);
2150 } else {
2151 return Err(GitError::InvalidFormat(format!(
2152 "unsupported object-info request argument {text}"
2153 )));
2154 }
2155 }
2156 if !out.size {
2157 return Err(GitError::InvalidFormat(
2158 "object-info request is missing size argument".into(),
2159 ));
2160 }
2161 if out.oids.is_empty() {
2162 return Err(GitError::InvalidFormat(
2163 "object-info request is missing object ids".into(),
2164 ));
2165 }
2166 Ok(out)
2167 }
2168
2169 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
2170 if !self.size {
2171 return Err(GitError::InvalidFormat(
2172 "object-info request is missing size argument".into(),
2173 ));
2174 }
2175 if self.oids.is_empty() {
2176 return Err(GitError::InvalidFormat(
2177 "object-info request is missing object ids".into(),
2178 ));
2179 }
2180 let mut request = ProtocolV2CommandRequest::new("object-info")?;
2181 request.arguments.push(b"size".to_vec());
2182 for oid in &self.oids {
2183 request.arguments.push(format!("oid {oid}").into_bytes());
2184 }
2185 Ok(request)
2186 }
2187}
2188
2189pub fn parse_protocol_v2_advertisement(frames: &[PktLineFrame]) -> Result<TransportHandshake> {
2190 let Some((first, rest)) = frames.split_first() else {
2191 return Err(GitError::InvalidFormat(
2192 "protocol v2 advertisement is empty".into(),
2193 ));
2194 };
2195 match first {
2196 PktLineFrame::Data(payload) if trim_trailing_lf(payload) == b"version 2" => {}
2197 PktLineFrame::Data(_) => {
2198 return Err(GitError::InvalidFormat(
2199 "protocol v2 advertisement missing version line".into(),
2200 ));
2201 }
2202 _ => {
2203 return Err(GitError::InvalidFormat(
2204 "protocol v2 advertisement must start with a data line".into(),
2205 ));
2206 }
2207 }
2208
2209 let mut capabilities = Vec::new();
2210 let mut saw_flush = false;
2211 for (idx, frame) in rest.iter().enumerate() {
2212 match frame {
2213 PktLineFrame::Data(payload) => {
2214 if saw_flush {
2215 return Err(GitError::InvalidFormat(
2216 "protocol v2 advertisement has data after flush".into(),
2217 ));
2218 }
2219 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2220 }
2221 PktLineFrame::Flush => {
2222 saw_flush = true;
2223 if idx + 1 != rest.len() {
2224 return Err(GitError::InvalidFormat(
2225 "protocol v2 advertisement has frames after flush".into(),
2226 ));
2227 }
2228 }
2229 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2230 return Err(GitError::InvalidFormat(
2231 "protocol v2 advertisement contains a non-flush control packet".into(),
2232 ));
2233 }
2234 }
2235 }
2236 if !saw_flush {
2237 return Err(GitError::InvalidFormat(
2238 "protocol v2 advertisement missing flush".into(),
2239 ));
2240 }
2241
2242 Ok(TransportHandshake {
2243 protocol: ProtocolVersion::V2,
2244 capabilities,
2245 })
2246}
2247
2248pub fn encode_protocol_v2_advertisement(
2249 handshake: &TransportHandshake,
2250) -> Result<Vec<PktLineFrame>> {
2251 if handshake.protocol != ProtocolVersion::V2 {
2252 return Err(GitError::InvalidFormat(
2253 "protocol v2 advertisement requires a v2 handshake".into(),
2254 ));
2255 }
2256 let mut frames = vec![PktLineFrame::data(line_from_str("version 2"))?];
2257 for capability in &handshake.capabilities {
2258 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2259 capability,
2260 )?))?);
2261 }
2262 frames.push(PktLineFrame::Flush);
2263 Ok(frames)
2264}
2265
2266pub fn read_protocol_v2_advertisement(reader: &mut impl Read) -> Result<TransportHandshake> {
2267 let frames = read_pkt_line_frames_until_flush(reader)?;
2268 parse_protocol_v2_advertisement(&frames)
2269}
2270
2271pub fn write_protocol_v2_advertisement(
2272 writer: &mut impl Write,
2273 handshake: &TransportHandshake,
2274) -> Result<()> {
2275 if handshake.protocol != ProtocolVersion::V2 {
2276 return Err(GitError::InvalidFormat(
2277 "protocol v2 advertisement requires a v2 handshake".into(),
2278 ));
2279 }
2280 write_pkt_line_payload(writer, b"version 2\n")?;
2281 for capability in &handshake.capabilities {
2282 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2283 }
2284 writer.write_all(b"0000")?;
2285 Ok(())
2286}
2287
2288pub fn parse_protocol_v2_command_request(
2289 frames: &[PktLineFrame],
2290) -> Result<ProtocolV2CommandRequest> {
2291 let Some((first, rest)) = frames.split_first() else {
2292 return Err(GitError::InvalidFormat(
2293 "protocol v2 command request is empty".into(),
2294 ));
2295 };
2296 let command = match first {
2297 PktLineFrame::Data(payload) => parse_protocol_v2_command_line(payload)?,
2298 _ => {
2299 return Err(GitError::InvalidFormat(
2300 "protocol v2 command request must start with a command line".into(),
2301 ));
2302 }
2303 };
2304
2305 let mut capabilities = Vec::new();
2306 let mut arguments = Vec::new();
2307 let mut in_arguments = false;
2308 let mut saw_flush = false;
2309 for (idx, frame) in rest.iter().enumerate() {
2310 match frame {
2311 PktLineFrame::Data(payload) if !in_arguments => {
2312 if saw_flush {
2313 return Err(GitError::InvalidFormat(
2314 "protocol v2 command request has data after flush".into(),
2315 ));
2316 }
2317 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2318 }
2319 PktLineFrame::Data(payload) => {
2320 if saw_flush {
2321 return Err(GitError::InvalidFormat(
2322 "protocol v2 command request has data after flush".into(),
2323 ));
2324 }
2325 let argument = trim_trailing_lf(payload);
2326 if argument.is_empty() {
2327 return Err(GitError::InvalidFormat(
2328 "protocol v2 command argument is empty".into(),
2329 ));
2330 }
2331 if argument
2332 .iter()
2333 .any(|byte| matches!(*byte, b'\n' | b'\r' | 0))
2334 {
2335 return Err(GitError::InvalidFormat(
2336 "protocol v2 command argument contains a delimiter byte".into(),
2337 ));
2338 }
2339 arguments.push(argument.to_vec());
2340 }
2341 PktLineFrame::Delimiter => {
2342 if in_arguments {
2343 return Err(GitError::InvalidFormat(format!(
2344 "expected flush after {} arguments",
2345 command
2346 )));
2347 }
2348 if saw_flush {
2349 return Err(GitError::InvalidFormat(
2350 "protocol v2 command request has delimiter after flush".into(),
2351 ));
2352 }
2353 in_arguments = true;
2354 }
2355 PktLineFrame::Flush => {
2356 saw_flush = true;
2357 if idx + 1 != rest.len() {
2358 return Err(GitError::InvalidFormat(
2359 "protocol v2 command request has frames after flush".into(),
2360 ));
2361 }
2362 }
2363 PktLineFrame::ResponseEnd => {
2364 return Err(GitError::InvalidFormat(
2365 "protocol v2 command request contains response-end".into(),
2366 ));
2367 }
2368 }
2369 }
2370 if !saw_flush {
2371 return Err(GitError::InvalidFormat(
2372 "protocol v2 command request missing flush".into(),
2373 ));
2374 }
2375
2376 Ok(ProtocolV2CommandRequest {
2377 command,
2378 capabilities,
2379 arguments,
2380 })
2381}
2382
2383pub fn encode_protocol_v2_command_request(
2384 request: &ProtocolV2CommandRequest,
2385) -> Result<Vec<PktLineFrame>> {
2386 validate_capability_name(&request.command)?;
2387 let mut frames = Vec::new();
2388 frames.push(PktLineFrame::data(line_from_str(&format!(
2389 "command={}",
2390 request.command
2391 )))?);
2392 for capability in &request.capabilities {
2393 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2394 capability,
2395 )?))?);
2396 }
2397 if !request.arguments.is_empty() {
2398 frames.push(PktLineFrame::Delimiter);
2399 for argument in &request.arguments {
2400 validate_protocol_v2_argument(argument)?;
2401 let mut payload = argument.clone();
2402 payload.push(b'\n');
2403 frames.push(PktLineFrame::data(payload)?);
2404 }
2405 }
2406 frames.push(PktLineFrame::Flush);
2407 Ok(frames)
2408}
2409
2410pub fn parse_protocol_v2_request(frames: &[PktLineFrame]) -> Result<ProtocolV2Request> {
2411 if matches!(frames, [PktLineFrame::Flush]) {
2412 return Ok(ProtocolV2Request::Done);
2413 }
2414 parse_protocol_v2_command_request(frames).map(ProtocolV2Request::Command)
2415}
2416
2417pub fn encode_protocol_v2_request(request: &ProtocolV2Request) -> Result<Vec<PktLineFrame>> {
2418 match request {
2419 ProtocolV2Request::Command(command) => encode_protocol_v2_command_request(command),
2420 ProtocolV2Request::Done => Ok(vec![PktLineFrame::Flush]),
2421 }
2422}
2423
2424pub fn read_protocol_v2_request(reader: &mut impl Read) -> Result<ProtocolV2Request> {
2425 let frames = read_pkt_line_frames_until_flush(reader)?;
2426 parse_protocol_v2_request(&frames)
2427}
2428
2429pub fn write_protocol_v2_request(
2430 writer: &mut impl Write,
2431 request: &ProtocolV2Request,
2432) -> Result<()> {
2433 match request {
2434 ProtocolV2Request::Command(command) => write_protocol_v2_command_request(writer, command),
2435 ProtocolV2Request::Done => {
2436 writer.write_all(b"0000")?;
2437 Ok(())
2438 }
2439 }
2440}
2441
2442pub fn read_protocol_v2_command_request(
2443 reader: &mut impl Read,
2444) -> Result<ProtocolV2CommandRequest> {
2445 let mut frames = Vec::new();
2446 loop {
2447 let Some(frame) = read_pkt_line_frame(reader)? else {
2448 if let Some(command) = frames.first().and_then(|frame| match frame {
2449 PktLineFrame::Data(payload) => parse_protocol_v2_command_line(payload).ok(),
2450 _ => None,
2451 }) && frames
2452 .iter()
2453 .any(|frame| matches!(frame, PktLineFrame::Delimiter))
2454 {
2455 return Err(GitError::InvalidFormat(format!(
2456 "expected flush after {} arguments",
2457 command
2458 )));
2459 }
2460 return Err(GitError::InvalidFormat(
2461 "pkt-line stream ended before control packet".into(),
2462 ));
2463 };
2464 let done = matches!(frame, PktLineFrame::Flush);
2465 frames.push(frame);
2466 if done {
2467 break;
2468 }
2469 }
2470 parse_protocol_v2_command_request(&frames)
2471}
2472
2473pub fn write_protocol_v2_command_request(
2474 writer: &mut impl Write,
2475 request: &ProtocolV2CommandRequest,
2476) -> Result<()> {
2477 validate_capability_name(&request.command)?;
2478 write_pkt_line_payload(
2479 writer,
2480 &line_from_str(&format!("command={}", request.command)),
2481 )?;
2482 for capability in &request.capabilities {
2483 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2484 }
2485 if !request.arguments.is_empty() {
2486 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2487 for argument in &request.arguments {
2488 validate_protocol_v2_argument(argument)?;
2489 let mut payload = argument.clone();
2490 payload.push(b'\n');
2491 write_pkt_line_payload(writer, &payload)?;
2492 }
2493 }
2494 writer.write_all(b"0000")?;
2495 Ok(())
2496}
2497
2498pub fn read_protocol_v2_ls_refs_request(reader: &mut impl Read) -> Result<ProtocolV2LsRefsRequest> {
2499 let request = read_protocol_v2_command_request(reader)?;
2500 ProtocolV2LsRefsRequest::from_command_request(&request)
2501}
2502
2503pub fn write_protocol_v2_ls_refs_request(
2504 writer: &mut impl Write,
2505 request: &ProtocolV2LsRefsRequest,
2506) -> Result<()> {
2507 let command = request.to_command_request()?;
2508 write_protocol_v2_command_request(writer, &command)
2509}
2510
2511pub fn parse_protocol_v2_ls_refs_response(
2512 format: ObjectFormat,
2513 frames: &[PktLineFrame],
2514) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2515 let mut records = Vec::new();
2516 let mut saw_flush = false;
2517 for (idx, frame) in frames.iter().enumerate() {
2518 match frame {
2519 PktLineFrame::Data(payload) => {
2520 if saw_flush {
2521 return Err(GitError::InvalidFormat(
2522 "ls-refs response has data after flush".into(),
2523 ));
2524 }
2525 records.push(parse_protocol_v2_ls_refs_line(format, payload)?);
2526 }
2527 PktLineFrame::Flush => {
2528 saw_flush = true;
2529 if !flush_terminates_protocol_v2_response(frames, idx) {
2530 return Err(GitError::InvalidFormat(
2531 "ls-refs response has frames after flush".into(),
2532 ));
2533 }
2534 }
2535 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2536 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2537 return Err(GitError::InvalidFormat(
2538 "ls-refs response contains a non-flush control packet".into(),
2539 ));
2540 }
2541 }
2542 }
2543 if !saw_flush {
2544 return Err(GitError::InvalidFormat(
2545 "ls-refs response missing flush".into(),
2546 ));
2547 }
2548 Ok(records)
2549}
2550
2551pub fn encode_protocol_v2_ls_refs_response(
2552 records: &[ProtocolV2LsRefsRecord],
2553) -> Result<Vec<PktLineFrame>> {
2554 let mut frames = Vec::new();
2555 for record in records {
2556 frames.push(PktLineFrame::data(line_from_str(
2557 &format_protocol_v2_ls_refs_record(record)?,
2558 ))?);
2559 }
2560 frames.push(PktLineFrame::Flush);
2561 Ok(frames)
2562}
2563
2564pub fn read_protocol_v2_ls_refs_response(
2565 format: ObjectFormat,
2566 reader: &mut impl Read,
2567) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2568 let frames = read_pkt_line_frames_until_flush(reader)?;
2569 parse_protocol_v2_ls_refs_response(format, &frames)
2570}
2571
2572pub fn write_protocol_v2_ls_refs_response(
2573 writer: &mut impl Write,
2574 records: &[ProtocolV2LsRefsRecord],
2575) -> Result<()> {
2576 for record in records {
2577 write_pkt_line_payload(
2578 writer,
2579 &line_from_str(&format_protocol_v2_ls_refs_record(record)?),
2580 )?;
2581 }
2582 writer.write_all(b"0000")?;
2583 Ok(())
2584}
2585
2586pub fn read_protocol_v2_ls_refs_response_until_response_end(
2587 format: ObjectFormat,
2588 reader: &mut impl Read,
2589) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2590 let frames = read_pkt_line_frames_until_response_end(reader)?;
2591 parse_protocol_v2_ls_refs_response(format, &frames)
2592}
2593
2594pub fn write_protocol_v2_ls_refs_response_with_response_end(
2595 writer: &mut impl Write,
2596 records: &[ProtocolV2LsRefsRecord],
2597) -> Result<()> {
2598 write_protocol_v2_ls_refs_response(writer, records)?;
2599 writer.write_all(b"0002")?;
2600 Ok(())
2601}
2602
2603pub fn exchange_protocol_v2_ls_refs(
2604 format: ObjectFormat,
2605 reader: &mut impl Read,
2606 writer: &mut impl Write,
2607 request: &ProtocolV2LsRefsRequest,
2608) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2609 write_protocol_v2_ls_refs_request(writer, request)?;
2610 writer.flush()?;
2611 read_protocol_v2_ls_refs_response(format, reader)
2612}
2613
2614pub fn protocol_v2_ls_refs_records_to_ref_advertisement_set(
2630 records: &[ProtocolV2LsRefsRecord],
2631) -> Result<RefAdvertisementSet> {
2632 let mut refs: Vec<RefAdvertisement> = Vec::new();
2633 let mut symrefs: Vec<Capability> = Vec::new();
2634 for record in records {
2635 match record {
2636 ProtocolV2LsRefsRecord::Ref(reference) => {
2637 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
2638 refs.push(RefAdvertisement {
2639 oid: reference.oid,
2640 name: reference.name.clone(),
2641 capabilities: Vec::new(),
2642 });
2643 if let Some(peeled) = &reference.peeled {
2644 refs.push(RefAdvertisement {
2645 oid: peeled.clone(),
2646 name: format!("{}^{{}}", reference.name),
2647 capabilities: Vec::new(),
2648 });
2649 }
2650 if let Some(target) = &reference.symref_target {
2651 symrefs.push(protocol_v2_symref_capability(&reference.name, target)?);
2652 }
2653 }
2654 ProtocolV2LsRefsRecord::Unborn {
2655 name,
2656 symref_target,
2657 ..
2658 } => {
2659 validate_protocol_v2_token("ls-refs ref name", name)?;
2660 if let Some(target) = symref_target {
2661 symrefs.push(protocol_v2_symref_capability(name, target)?);
2662 }
2663 }
2664 }
2665 }
2666 if !symrefs.is_empty() {
2667 if let Some(first) = refs.first_mut() {
2668 first.capabilities = symrefs;
2669 } else {
2670 return Err(GitError::InvalidFormat(
2671 "ls-refs response advertised symrefs without any concrete refs".into(),
2672 ));
2673 }
2674 }
2675 Ok(RefAdvertisementSet {
2676 protocol: ProtocolVersion::V2,
2677 refs,
2678 shallow: Vec::new(),
2679 })
2680}
2681
2682pub fn parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2687 format: ObjectFormat,
2688 frames: &[PktLineFrame],
2689) -> Result<RefAdvertisementSet> {
2690 let records = parse_protocol_v2_ls_refs_response(format, frames)?;
2691 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2692}
2693
2694pub fn read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2697 format: ObjectFormat,
2698 reader: &mut impl Read,
2699) -> Result<RefAdvertisementSet> {
2700 let records = read_protocol_v2_ls_refs_response(format, reader)?;
2701 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2702}
2703
2704fn protocol_v2_symref_capability(name: &str, target: &str) -> Result<Capability> {
2705 validate_protocol_v2_token("ls-refs symref-target", target)?;
2706 Ok(Capability {
2707 name: "symref".into(),
2708 value: Some(format!("{name}:{target}")),
2709 })
2710}
2711
2712pub fn read_protocol_v2_fetch_request(
2713 format: ObjectFormat,
2714 reader: &mut impl Read,
2715) -> Result<ProtocolV2FetchRequest> {
2716 let request = read_protocol_v2_command_request(reader)?;
2717 ProtocolV2FetchRequest::from_command_request(format, &request)
2718}
2719
2720pub fn write_protocol_v2_fetch_request(
2721 writer: &mut impl Write,
2722 request: &ProtocolV2FetchRequest,
2723) -> Result<()> {
2724 let command = request.to_command_request()?;
2725 write_protocol_v2_command_request(writer, &command)
2726}
2727
2728pub fn read_protocol_v2_object_info_request(
2729 format: ObjectFormat,
2730 reader: &mut impl Read,
2731) -> Result<ProtocolV2ObjectInfoRequest> {
2732 let request = read_protocol_v2_command_request(reader)?;
2733 ProtocolV2ObjectInfoRequest::from_command_request(format, &request)
2734}
2735
2736pub fn write_protocol_v2_object_info_request(
2737 writer: &mut impl Write,
2738 request: &ProtocolV2ObjectInfoRequest,
2739) -> Result<()> {
2740 let command = request.to_command_request()?;
2741 write_protocol_v2_command_request(writer, &command)
2742}
2743
2744pub fn parse_protocol_v2_fetch_response(
2745 format: ObjectFormat,
2746 frames: &[PktLineFrame],
2747) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2748 let mut sections = Vec::new();
2749 let mut current: Option<(String, Vec<Vec<u8>>)> = None;
2750 let mut saw_flush = false;
2751 for (idx, frame) in frames.iter().enumerate() {
2752 match frame {
2753 PktLineFrame::Data(payload) => {
2754 if saw_flush {
2755 return Err(GitError::InvalidFormat(
2756 "fetch response has data after flush".into(),
2757 ));
2758 }
2759 if let Some((_name, lines)) = &mut current {
2760 lines.push(payload.clone());
2761 } else {
2762 let name = parse_fetch_section_header(payload)?;
2763 current = Some((name, Vec::new()));
2764 }
2765 }
2766 PktLineFrame::Delimiter => {
2767 if saw_flush {
2768 return Err(GitError::InvalidFormat(
2769 "fetch response has delimiter after flush".into(),
2770 ));
2771 }
2772 let Some((name, lines)) = current.take() else {
2773 return Err(GitError::InvalidFormat(
2774 "fetch response has delimiter before section".into(),
2775 ));
2776 };
2777 sections.push(parse_fetch_section(format, name, lines)?);
2778 }
2779 PktLineFrame::Flush => {
2780 saw_flush = true;
2781 if !flush_terminates_protocol_v2_response(frames, idx) {
2782 return Err(GitError::InvalidFormat(
2783 "fetch response has frames after flush".into(),
2784 ));
2785 }
2786 if let Some((name, lines)) = current.take() {
2787 sections.push(parse_fetch_section(format, name, lines)?);
2788 }
2789 }
2790 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2791 PktLineFrame::ResponseEnd => {
2792 return Err(GitError::InvalidFormat(
2793 "fetch response contains response-end".into(),
2794 ));
2795 }
2796 }
2797 }
2798 if !saw_flush {
2799 return Err(GitError::InvalidFormat(
2800 "fetch response missing flush".into(),
2801 ));
2802 }
2803 Ok(sections)
2804}
2805
2806pub fn encode_protocol_v2_fetch_response(
2807 sections: &[ProtocolV2FetchResponseSection],
2808) -> Result<Vec<PktLineFrame>> {
2809 let mut frames = Vec::new();
2810 for (idx, section) in sections.iter().enumerate() {
2811 if idx != 0 {
2812 frames.push(PktLineFrame::Delimiter);
2813 }
2814 frames.push(PktLineFrame::data(line_from_str(
2815 protocol_v2_fetch_section_name(section),
2816 ))?);
2817 for line in format_protocol_v2_fetch_section_lines(section)? {
2818 frames.push(PktLineFrame::data(line)?);
2819 }
2820 }
2821 frames.push(PktLineFrame::Flush);
2822 Ok(frames)
2823}
2824
2825pub fn parse_protocol_v2_fetch_sideband_all_response(
2826 format: ObjectFormat,
2827 frames: &[PktLineFrame],
2828) -> Result<ProtocolV2FetchSidebandAllResponse> {
2829 let mut demuxed = Vec::new();
2830 let mut progress = Vec::new();
2831 let mut in_packfile = false;
2832 for frame in frames {
2833 match frame {
2834 PktLineFrame::Data(payload) if in_packfile => {
2835 demuxed.push(PktLineFrame::Data(payload.clone()));
2836 }
2837 PktLineFrame::Data(payload) => {
2838 let packet = parse_sideband_packet(payload)?;
2839 match packet.channel {
2840 SideBandChannel::Data => {
2841 if trim_trailing_lf(&packet.data) == b"packfile" {
2842 in_packfile = true;
2843 }
2844 demuxed.push(PktLineFrame::Data(packet.data));
2845 }
2846 SideBandChannel::Progress => progress.push(packet.data),
2847 SideBandChannel::Fatal => {
2848 let message = String::from_utf8_lossy(&packet.data).into_owned();
2849 return Err(GitError::InvalidFormat(format!(
2850 "sideband fatal: {message}"
2851 )));
2852 }
2853 }
2854 }
2855 PktLineFrame::Delimiter => {
2856 in_packfile = false;
2857 demuxed.push(PktLineFrame::Delimiter);
2858 }
2859 PktLineFrame::Flush => {
2860 in_packfile = false;
2861 demuxed.push(PktLineFrame::Flush);
2862 }
2863 PktLineFrame::ResponseEnd => {
2864 in_packfile = false;
2865 demuxed.push(PktLineFrame::ResponseEnd);
2866 }
2867 }
2868 }
2869 Ok(ProtocolV2FetchSidebandAllResponse {
2870 sections: parse_protocol_v2_fetch_response(format, &demuxed)?,
2871 progress,
2872 })
2873}
2874
2875pub fn encode_protocol_v2_fetch_sideband_all_response(
2876 sections: &[ProtocolV2FetchResponseSection],
2877) -> Result<Vec<PktLineFrame>> {
2878 let frames = encode_protocol_v2_fetch_response(sections)?;
2879 let mut encoded = Vec::new();
2880 let mut in_packfile = false;
2881 for frame in frames {
2882 match frame {
2883 PktLineFrame::Data(payload) if in_packfile => {
2884 encoded.push(PktLineFrame::Data(payload));
2885 }
2886 PktLineFrame::Data(payload) => {
2887 if trim_trailing_lf(&payload) == b"packfile" {
2888 in_packfile = true;
2889 }
2890 encoded.push(PktLineFrame::data(encode_sideband_packet(
2891 &SideBandPacket {
2892 channel: SideBandChannel::Data,
2893 data: payload,
2894 },
2895 )?)?);
2896 }
2897 PktLineFrame::Delimiter => {
2898 in_packfile = false;
2899 encoded.push(PktLineFrame::Delimiter);
2900 }
2901 PktLineFrame::Flush => {
2902 in_packfile = false;
2903 encoded.push(PktLineFrame::Flush);
2904 }
2905 PktLineFrame::ResponseEnd => {
2906 in_packfile = false;
2907 encoded.push(PktLineFrame::ResponseEnd);
2908 }
2909 }
2910 }
2911 Ok(encoded)
2912}
2913
2914pub fn read_protocol_v2_fetch_response(
2915 format: ObjectFormat,
2916 reader: &mut impl Read,
2917) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2918 let frames = read_pkt_line_frames_until_flush(reader)?;
2919 parse_protocol_v2_fetch_response(format, &frames)
2920}
2921
2922pub fn write_protocol_v2_fetch_response(
2923 writer: &mut impl Write,
2924 sections: &[ProtocolV2FetchResponseSection],
2925) -> Result<()> {
2926 write_protocol_v2_fetch_response_inner(writer, sections, false, false)
2927}
2928
2929pub fn read_protocol_v2_fetch_sideband_all_response(
2930 format: ObjectFormat,
2931 reader: &mut impl Read,
2932) -> Result<ProtocolV2FetchSidebandAllResponse> {
2933 let frames = read_pkt_line_frames_until_flush(reader)?;
2934 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2935}
2936
2937pub fn write_protocol_v2_fetch_sideband_all_response(
2938 writer: &mut impl Write,
2939 sections: &[ProtocolV2FetchResponseSection],
2940) -> Result<()> {
2941 write_protocol_v2_fetch_response_inner(writer, sections, true, false)
2942}
2943
2944pub fn read_protocol_v2_fetch_response_until_response_end(
2945 format: ObjectFormat,
2946 reader: &mut impl Read,
2947) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2948 let frames = read_pkt_line_frames_until_response_end(reader)?;
2949 parse_protocol_v2_fetch_response(format, &frames)
2950}
2951
2952pub fn write_protocol_v2_fetch_response_with_response_end(
2953 writer: &mut impl Write,
2954 sections: &[ProtocolV2FetchResponseSection],
2955) -> Result<()> {
2956 write_protocol_v2_fetch_response_inner(writer, sections, false, true)
2957}
2958
2959pub fn read_protocol_v2_fetch_sideband_all_response_until_response_end(
2960 format: ObjectFormat,
2961 reader: &mut impl Read,
2962) -> Result<ProtocolV2FetchSidebandAllResponse> {
2963 let frames = read_pkt_line_frames_until_response_end(reader)?;
2964 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2965}
2966
2967pub fn write_protocol_v2_fetch_sideband_all_response_with_response_end(
2968 writer: &mut impl Write,
2969 sections: &[ProtocolV2FetchResponseSection],
2970) -> Result<()> {
2971 write_protocol_v2_fetch_response_inner(writer, sections, true, true)
2972}
2973
2974fn write_protocol_v2_fetch_response_inner(
2975 writer: &mut impl Write,
2976 sections: &[ProtocolV2FetchResponseSection],
2977 sideband_all: bool,
2978 response_end: bool,
2979) -> Result<()> {
2980 let mut in_packfile = false;
2981 for (idx, section) in sections.iter().enumerate() {
2982 if idx != 0 {
2983 in_packfile = false;
2984 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2985 }
2986 write_protocol_v2_fetch_payload(
2987 writer,
2988 &line_from_str(protocol_v2_fetch_section_name(section)),
2989 sideband_all,
2990 &mut in_packfile,
2991 )?;
2992 for payload in format_protocol_v2_fetch_section_lines(section)? {
2993 write_protocol_v2_fetch_payload(writer, &payload, sideband_all, &mut in_packfile)?;
2994 }
2995 }
2996 writer.write_all(b"0000")?;
2997 if response_end {
2998 writer.write_all(b"0002")?;
2999 }
3000 Ok(())
3001}
3002
3003fn write_protocol_v2_fetch_payload(
3004 writer: &mut impl Write,
3005 payload: &[u8],
3006 sideband_all: bool,
3007 in_packfile: &mut bool,
3008) -> Result<()> {
3009 if sideband_all && !*in_packfile {
3010 if trim_trailing_lf(payload) == b"packfile" {
3011 *in_packfile = true;
3012 }
3013 write_sideband_payload(writer, SideBandChannel::Data, payload)
3014 } else {
3015 write_pkt_line_payload(writer, payload)
3016 }
3017}
3018
3019pub fn exchange_protocol_v2_fetch(
3020 format: ObjectFormat,
3021 reader: &mut impl Read,
3022 writer: &mut impl Write,
3023 request: &ProtocolV2FetchRequest,
3024) -> Result<Vec<ProtocolV2FetchResponseSection>> {
3025 write_protocol_v2_fetch_request(writer, request)?;
3026 writer.flush()?;
3027 read_protocol_v2_fetch_response(format, reader)
3028}
3029
3030pub fn parse_protocol_v2_object_info_response(
3031 format: ObjectFormat,
3032 frames: &[PktLineFrame],
3033) -> Result<ProtocolV2ObjectInfoResponse> {
3034 let Some((first, rest)) = frames.split_first() else {
3035 return Err(GitError::InvalidFormat(
3036 "object-info response is empty".into(),
3037 ));
3038 };
3039 let PktLineFrame::Data(attrs) = first else {
3040 return Err(GitError::InvalidFormat(
3041 "object-info response must start with attributes".into(),
3042 ));
3043 };
3044 let attrs = parse_protocol_v2_line_text("object-info response attributes", attrs)?;
3045 let mut response = ProtocolV2ObjectInfoResponse::default();
3046 for attr in attrs.split(' ') {
3047 validate_protocol_v2_token("object-info response attribute", attr)?;
3048 match attr {
3049 "size" => {
3050 if response.size {
3051 return Err(GitError::InvalidFormat(
3052 "object-info response has duplicate size attribute".into(),
3053 ));
3054 }
3055 response.size = true;
3056 }
3057 other => {
3058 return Err(GitError::InvalidFormat(format!(
3059 "unsupported object-info response attribute {other}"
3060 )));
3061 }
3062 }
3063 }
3064 if !response.size {
3065 return Err(GitError::InvalidFormat(
3066 "object-info response is missing size attribute".into(),
3067 ));
3068 }
3069
3070 let mut saw_flush = false;
3071 for (idx, frame) in rest.iter().enumerate() {
3072 match frame {
3073 PktLineFrame::Data(payload) if !saw_flush => {
3074 response
3075 .records
3076 .push(parse_protocol_v2_object_info_record(format, payload)?);
3077 }
3078 PktLineFrame::Data(_) => {
3079 return Err(GitError::InvalidFormat(
3080 "object-info response has data after flush".into(),
3081 ));
3082 }
3083 PktLineFrame::Flush => {
3084 saw_flush = true;
3085 if idx + 1 != rest.len() {
3086 return Err(GitError::InvalidFormat(
3087 "object-info response has frames after flush".into(),
3088 ));
3089 }
3090 }
3091 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3092 return Err(GitError::InvalidFormat(
3093 "object-info response contains a non-flush control packet".into(),
3094 ));
3095 }
3096 }
3097 }
3098 if !saw_flush {
3099 return Err(GitError::InvalidFormat(
3100 "object-info response missing flush".into(),
3101 ));
3102 }
3103 Ok(response)
3104}
3105
3106pub fn encode_protocol_v2_object_info_response(
3107 response: &ProtocolV2ObjectInfoResponse,
3108) -> Result<Vec<PktLineFrame>> {
3109 if !response.size {
3110 return Err(GitError::InvalidFormat(
3111 "object-info response is missing size attribute".into(),
3112 ));
3113 }
3114 let mut frames = Vec::new();
3115 frames.push(PktLineFrame::data(line_from_str("size"))?);
3116 for record in &response.records {
3117 frames.push(PktLineFrame::data(line_from_str(&format!(
3118 "{} {}",
3119 record.oid, record.size
3120 )))?);
3121 }
3122 frames.push(PktLineFrame::Flush);
3123 Ok(frames)
3124}
3125
3126pub fn read_protocol_v2_object_info_response(
3127 format: ObjectFormat,
3128 reader: &mut impl Read,
3129) -> Result<ProtocolV2ObjectInfoResponse> {
3130 let frames = read_pkt_line_frames_until_flush(reader)?;
3131 parse_protocol_v2_object_info_response(format, &frames)
3132}
3133
3134pub fn write_protocol_v2_object_info_response(
3135 writer: &mut impl Write,
3136 response: &ProtocolV2ObjectInfoResponse,
3137) -> Result<()> {
3138 if !response.size {
3139 return Err(GitError::InvalidFormat(
3140 "object-info response is missing size attribute".into(),
3141 ));
3142 }
3143 write_pkt_line_payload(writer, b"size\n")?;
3144 for record in &response.records {
3145 write_pkt_line_payload(
3146 writer,
3147 &line_from_str(&format!("{} {}", record.oid, record.size)),
3148 )?;
3149 }
3150 writer.write_all(b"0000")?;
3151 Ok(())
3152}
3153
3154pub fn exchange_protocol_v2_object_info(
3155 format: ObjectFormat,
3156 reader: &mut impl Read,
3157 writer: &mut impl Write,
3158 request: &ProtocolV2ObjectInfoRequest,
3159) -> Result<ProtocolV2ObjectInfoResponse> {
3160 write_protocol_v2_object_info_request(writer, request)?;
3161 writer.flush()?;
3162 read_protocol_v2_object_info_response(format, reader)
3163}
3164
3165pub fn demux_protocol_v2_fetch_packfile(
3166 sections: &[ProtocolV2FetchResponseSection],
3167) -> Result<Option<SideBandDemux>> {
3168 let mut packfile = None;
3169 for section in sections {
3170 if let ProtocolV2FetchResponseSection::Packfile(lines) = section {
3171 if packfile.is_some() {
3172 return Err(GitError::InvalidFormat(
3173 "fetch response has duplicate packfile sections".into(),
3174 ));
3175 }
3176 packfile = Some(parse_and_demux_sideband_packets(lines)?);
3177 }
3178 }
3179 Ok(packfile)
3180}
3181
3182pub fn protocol_v2_object_format(capabilities: &[Capability]) -> Result<ObjectFormat> {
3183 let mut format = None;
3184 for capability in capabilities {
3185 if capability.name != "object-format" {
3186 continue;
3187 }
3188 if format.is_some() {
3189 return Err(GitError::InvalidFormat(
3190 "protocol v2 has duplicate object-format capabilities".into(),
3191 ));
3192 }
3193 let Some(value) = &capability.value else {
3194 return Err(GitError::InvalidFormat(
3195 "protocol v2 object-format capability is missing a value".into(),
3196 ));
3197 };
3198 format = Some(value.parse::<ObjectFormat>()?);
3199 }
3200 Ok(format.unwrap_or(ObjectFormat::Sha1))
3201}
3202
3203pub fn validate_protocol_v2_command_request_capabilities(
3204 handshake: &TransportHandshake,
3205 request: &ProtocolV2CommandRequest,
3206) -> Result<()> {
3207 if handshake.protocol != ProtocolVersion::V2 {
3208 return Err(GitError::InvalidFormat(
3209 "protocol v2 command validation requires a v2 handshake".into(),
3210 ));
3211 }
3212 let advertised =
3213 protocol_v2_capability(&handshake.capabilities, &request.command).ok_or_else(|| {
3214 GitError::InvalidFormat(format!("unadvertised command {}", request.command))
3215 })?;
3216 if advertised.name.is_empty() {
3217 return Err(GitError::InvalidFormat(
3218 "advertised command capability is empty".into(),
3219 ));
3220 }
3221 parse_protocol_v2_command_options(&request.capabilities)?;
3222
3223 for capability in &request.capabilities {
3224 let advertised = protocol_v2_capability(&handshake.capabilities, &capability.name)
3225 .ok_or_else(|| {
3226 GitError::InvalidFormat(format!(
3227 "unadvertised protocol v2 capability {}",
3228 capability.name
3229 ))
3230 })?;
3231 if capability.name == "object-format" {
3232 validate_protocol_v2_object_format_request(advertised, capability)?;
3233 }
3234 }
3235 Ok(())
3236}
3237
3238pub fn parse_protocol_v2_command_options(
3239 capabilities: &[Capability],
3240) -> Result<ProtocolV2CommandOptions> {
3241 let mut out = ProtocolV2CommandOptions::default();
3242 for capability in capabilities {
3243 match capability.name.as_str() {
3244 "agent" => {
3245 if out.agent.is_some() {
3246 return Err(GitError::InvalidFormat(
3247 "protocol v2 command has duplicate agent capabilities".into(),
3248 ));
3249 }
3250 let Some(value) = &capability.value else {
3251 return Err(GitError::InvalidFormat(
3252 "protocol v2 agent capability is missing a value".into(),
3253 ));
3254 };
3255 validate_protocol_v2_capability_value(value)?;
3256 out.agent = Some(value.clone());
3257 }
3258 "object-format" => {
3259 if out.object_format.is_some() {
3260 return Err(GitError::InvalidFormat(
3261 "protocol v2 command has duplicate object-format capabilities".into(),
3262 ));
3263 }
3264 let Some(value) = &capability.value else {
3265 return Err(GitError::InvalidFormat(
3266 "protocol v2 object-format capability is missing a value".into(),
3267 ));
3268 };
3269 out.object_format = Some(value.parse::<ObjectFormat>()?);
3270 }
3271 "server-option" => {
3272 let Some(value) = &capability.value else {
3273 return Err(GitError::InvalidFormat(
3274 "protocol v2 server-option capability is missing a value".into(),
3275 ));
3276 };
3277 validate_protocol_v2_capability_value(value)?;
3278 out.server_options.push(value.clone());
3279 }
3280 _ => out.extra.push(capability.clone()),
3281 }
3282 }
3283 Ok(out)
3284}
3285
3286pub fn encode_protocol_v2_command_options(
3287 options: &ProtocolV2CommandOptions,
3288) -> Result<Vec<Capability>> {
3289 let mut capabilities = Vec::new();
3290 if let Some(agent) = &options.agent {
3291 validate_protocol_v2_capability_value(agent)?;
3292 capabilities.push(Capability {
3293 name: "agent".into(),
3294 value: Some(agent.clone()),
3295 });
3296 }
3297 if let Some(format) = options.object_format {
3298 capabilities.push(Capability {
3299 name: "object-format".into(),
3300 value: Some(format.name().into()),
3301 });
3302 }
3303 for option in &options.server_options {
3304 validate_protocol_v2_capability_value(option)?;
3305 capabilities.push(Capability {
3306 name: "server-option".into(),
3307 value: Some(option.clone()),
3308 });
3309 }
3310 for capability in &options.extra {
3311 if matches!(
3312 capability.name.as_str(),
3313 "agent" | "object-format" | "server-option"
3314 ) {
3315 return Err(GitError::InvalidFormat(format!(
3316 "protocol v2 extra capability duplicates known capability {}",
3317 capability.name
3318 )));
3319 }
3320 encode_protocol_v2_capability(capability)?;
3321 capabilities.push(capability.clone());
3322 }
3323 Ok(capabilities)
3324}
3325
3326pub fn parse_protocol_v2_ls_refs_features(
3327 capabilities: &[Capability],
3328) -> Result<Option<ProtocolV2LsRefsFeatures>> {
3329 let mut ls_refs = None;
3330 for capability in capabilities {
3331 if capability.name != "ls-refs" {
3332 continue;
3333 }
3334 if ls_refs.is_some() {
3335 return Err(GitError::InvalidFormat(
3336 "protocol v2 has duplicate ls-refs capabilities".into(),
3337 ));
3338 }
3339 ls_refs = Some(parse_protocol_v2_ls_refs_feature_value(
3340 capability.value.as_deref(),
3341 )?);
3342 }
3343 Ok(ls_refs)
3344}
3345
3346pub fn encode_protocol_v2_ls_refs_capability(
3347 features: &ProtocolV2LsRefsFeatures,
3348) -> Result<Capability> {
3349 let mut values = Vec::new();
3350 if features.unborn {
3351 values.push("unborn".to_string());
3352 }
3353 for feature in &features.unknown {
3354 validate_protocol_v2_token("ls-refs feature", feature)?;
3355 if feature == "unborn" {
3356 return Err(GitError::InvalidFormat(
3357 "ls-refs unknown features must not duplicate known feature unborn".into(),
3358 ));
3359 }
3360 values.push(feature.clone());
3361 }
3362 Ok(Capability {
3363 name: "ls-refs".into(),
3364 value: (!values.is_empty()).then(|| values.join(" ")),
3365 })
3366}
3367
3368pub fn validate_protocol_v2_ls_refs_request_features(
3369 features: &ProtocolV2LsRefsFeatures,
3370 request: &ProtocolV2LsRefsRequest,
3371) -> Result<()> {
3372 if request.unborn && !features.unborn {
3373 return Err(GitError::InvalidFormat(
3374 "ls-refs request uses unborn without advertised unborn feature".into(),
3375 ));
3376 }
3377 Ok(())
3378}
3379
3380pub fn validate_protocol_v2_ls_refs_command_request(
3381 handshake: &TransportHandshake,
3382 request: &ProtocolV2CommandRequest,
3383) -> Result<ProtocolV2LsRefsRequest> {
3384 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3385 let ls_refs = ProtocolV2LsRefsRequest::from_command_request(request)?;
3386 let features = parse_protocol_v2_ls_refs_features(&handshake.capabilities)?
3387 .ok_or_else(|| GitError::InvalidFormat("ls-refs command was not advertised".into()))?;
3388 validate_protocol_v2_ls_refs_request_features(&features, &ls_refs)?;
3389 Ok(ls_refs)
3390}
3391
3392pub fn parse_protocol_v2_fetch_features(
3393 capabilities: &[Capability],
3394) -> Result<Option<ProtocolV2FetchFeatures>> {
3395 let mut fetch = None;
3396 for capability in capabilities {
3397 if capability.name != "fetch" {
3398 continue;
3399 }
3400 if fetch.is_some() {
3401 return Err(GitError::InvalidFormat(
3402 "protocol v2 has duplicate fetch capabilities".into(),
3403 ));
3404 }
3405 fetch = Some(parse_protocol_v2_fetch_feature_value(
3406 capability.value.as_deref(),
3407 )?);
3408 }
3409 Ok(fetch)
3410}
3411
3412pub fn encode_protocol_v2_fetch_capability(
3413 features: &ProtocolV2FetchFeatures,
3414) -> Result<Capability> {
3415 let mut values = Vec::new();
3416 if features.shallow {
3417 values.push("shallow".to_string());
3418 }
3419 if features.wait_for_done {
3420 values.push("wait-for-done".to_string());
3421 }
3422 if features.filter {
3423 values.push("filter".to_string());
3424 }
3425 if features.ref_in_want {
3426 values.push("ref-in-want".to_string());
3427 }
3428 if features.sideband_all {
3429 values.push("sideband-all".to_string());
3430 }
3431 if features.packfile_uris {
3432 values.push("packfile-uris".to_string());
3433 }
3434 for feature in &features.unknown {
3435 validate_protocol_v2_token("fetch feature", feature)?;
3436 if matches!(
3437 feature.as_str(),
3438 "shallow"
3439 | "wait-for-done"
3440 | "filter"
3441 | "ref-in-want"
3442 | "sideband-all"
3443 | "packfile-uris"
3444 ) {
3445 return Err(GitError::InvalidFormat(format!(
3446 "fetch unknown features must not duplicate known feature {feature}"
3447 )));
3448 }
3449 values.push(feature.clone());
3450 }
3451 Ok(Capability {
3452 name: "fetch".into(),
3453 value: (!values.is_empty()).then(|| values.join(" ")),
3454 })
3455}
3456
3457pub fn validate_protocol_v2_fetch_request_features(
3458 features: &ProtocolV2FetchFeatures,
3459 request: &ProtocolV2FetchRequest,
3460) -> Result<()> {
3461 if !features.shallow
3462 && (!request.shallow.is_empty()
3463 || request.deepen.is_some()
3464 || request.deepen_since.is_some()
3465 || !request.deepen_not.is_empty()
3466 || request.deepen_relative)
3467 {
3468 return Err(GitError::InvalidFormat(
3469 "fetch request uses shallow/deepen arguments without advertised shallow feature".into(),
3470 ));
3471 }
3472 if !features.filter && request.filter.is_some() {
3473 return Err(GitError::InvalidFormat(
3474 "fetch request uses filter without advertised filter feature".into(),
3475 ));
3476 }
3477 if !features.ref_in_want && !request.want_refs.is_empty() {
3478 return Err(GitError::InvalidFormat(
3479 "fetch request uses want-ref without advertised ref-in-want feature".into(),
3480 ));
3481 }
3482 if !features.sideband_all && request.sideband_all {
3483 return Err(GitError::InvalidFormat(
3484 "fetch request uses sideband-all without advertised sideband-all feature".into(),
3485 ));
3486 }
3487 if !features.packfile_uris && request.packfile_uris.is_some() {
3488 return Err(GitError::InvalidFormat(
3489 "fetch request uses packfile-uris without advertised packfile-uris feature".into(),
3490 ));
3491 }
3492 if !features.wait_for_done && request.wait_for_done {
3493 return Err(GitError::InvalidFormat(
3494 "fetch request uses wait-for-done without advertised wait-for-done feature".into(),
3495 ));
3496 }
3497 Ok(())
3498}
3499
3500pub fn validate_protocol_v2_fetch_command_request(
3501 handshake: &TransportHandshake,
3502 format: ObjectFormat,
3503 request: &ProtocolV2CommandRequest,
3504) -> Result<ProtocolV2FetchRequest> {
3505 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3506 let fetch = ProtocolV2FetchRequest::from_command_request(format, request)?;
3507 let features = parse_protocol_v2_fetch_features(&handshake.capabilities)?
3508 .ok_or_else(|| GitError::InvalidFormat("fetch command was not advertised".into()))?;
3509 validate_protocol_v2_fetch_request_features(&features, &fetch)?;
3510 Ok(fetch)
3511}
3512
3513pub fn validate_protocol_v2_object_info_command_request(
3514 handshake: &TransportHandshake,
3515 format: ObjectFormat,
3516 request: &ProtocolV2CommandRequest,
3517) -> Result<ProtocolV2ObjectInfoRequest> {
3518 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3519 let object_info = ProtocolV2ObjectInfoRequest::from_command_request(format, request)?;
3520 protocol_v2_capability(&handshake.capabilities, "object-info")
3521 .ok_or_else(|| GitError::InvalidFormat("object-info command was not advertised".into()))?;
3522 Ok(object_info)
3523}
3524
3525pub fn classify_protocol_v2_command_request(
3526 handshake: &TransportHandshake,
3527 format: ObjectFormat,
3528 request: &ProtocolV2CommandRequest,
3529) -> Result<ProtocolV2Command> {
3530 match request.command.as_str() {
3531 "ls-refs" => validate_protocol_v2_ls_refs_command_request(handshake, request)
3532 .map(ProtocolV2Command::LsRefs),
3533 "fetch" => validate_protocol_v2_fetch_command_request(handshake, format, request)
3534 .map(ProtocolV2Command::Fetch),
3535 "object-info" => {
3536 validate_protocol_v2_object_info_command_request(handshake, format, request)
3537 .map(ProtocolV2Command::ObjectInfo)
3538 }
3539 _ => {
3540 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3541 Ok(ProtocolV2Command::Unknown(request.clone()))
3542 }
3543 }
3544}
3545
3546pub fn classify_protocol_v2_request(
3547 handshake: &TransportHandshake,
3548 format: ObjectFormat,
3549 request: &ProtocolV2Request,
3550) -> Result<ProtocolV2SessionRequest> {
3551 match request {
3552 ProtocolV2Request::Command(command) => {
3553 classify_protocol_v2_command_request(handshake, format, command)
3554 .map(ProtocolV2SessionRequest::Command)
3555 }
3556 ProtocolV2Request::Done => Ok(ProtocolV2SessionRequest::Done),
3557 }
3558}
3559
3560pub fn read_protocol_v2_session_request(
3561 handshake: &TransportHandshake,
3562 format: ObjectFormat,
3563 reader: &mut impl Read,
3564) -> Result<ProtocolV2SessionRequest> {
3565 let request = read_protocol_v2_request(reader)?;
3566 classify_protocol_v2_request(handshake, format, &request)
3567}
3568
3569fn protocol_v2_capability<'a>(
3570 capabilities: &'a [Capability],
3571 name: &str,
3572) -> Option<&'a Capability> {
3573 capabilities
3574 .iter()
3575 .find(|capability| capability.name == name)
3576}
3577
3578fn validate_protocol_v2_object_format_request(
3579 advertised: &Capability,
3580 requested: &Capability,
3581) -> Result<()> {
3582 let Some(advertised) = &advertised.value else {
3583 return Err(GitError::InvalidFormat(
3584 "advertised object-format capability is missing a value".into(),
3585 ));
3586 };
3587 let Some(requested) = &requested.value else {
3588 return Err(GitError::InvalidFormat(
3589 "requested object-format capability is missing a value".into(),
3590 ));
3591 };
3592 if advertised != requested {
3593 return Err(GitError::InvalidFormat(format!(
3594 "requested object-format {requested} does not match advertised {advertised}"
3595 )));
3596 }
3597 Ok(())
3598}
3599
3600fn parse_protocol_v2_ls_refs_feature_value(
3601 value: Option<&str>,
3602) -> Result<ProtocolV2LsRefsFeatures> {
3603 let mut out = ProtocolV2LsRefsFeatures::default();
3604 let Some(value) = value else {
3605 return Ok(out);
3606 };
3607 if value.is_empty() {
3608 return Err(GitError::InvalidFormat(
3609 "protocol v2 ls-refs capability value is empty".into(),
3610 ));
3611 }
3612 for feature in value.split(' ') {
3613 validate_protocol_v2_token("ls-refs feature", feature)?;
3614 match feature {
3615 "unborn" => out.unborn = true,
3616 other => out.unknown.push(other.to_string()),
3617 }
3618 }
3619 Ok(out)
3620}
3621
3622fn parse_protocol_v2_fetch_feature_value(value: Option<&str>) -> Result<ProtocolV2FetchFeatures> {
3623 let mut out = ProtocolV2FetchFeatures::default();
3624 let Some(value) = value else {
3625 return Ok(out);
3626 };
3627 if value.is_empty() {
3628 return Err(GitError::InvalidFormat(
3629 "protocol v2 fetch capability value is empty".into(),
3630 ));
3631 }
3632 for feature in value.split(' ') {
3633 validate_protocol_v2_token("fetch feature", feature)?;
3634 match feature {
3635 "shallow" => out.shallow = true,
3636 "wait-for-done" => out.wait_for_done = true,
3637 "filter" => out.filter = true,
3638 "ref-in-want" => out.ref_in_want = true,
3639 "sideband-all" => out.sideband_all = true,
3640 "packfile-uris" => out.packfile_uris = true,
3641 other => out.unknown.push(other.to_string()),
3642 }
3643 }
3644 Ok(out)
3645}
3646
3647pub fn parse_capabilities(input: &[u8]) -> Result<Vec<Capability>> {
3648 let input = trim_trailing_lf(input);
3649 if input.is_empty() {
3650 return Ok(Vec::new());
3651 }
3652 let text =
3653 std::str::from_utf8(input).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3654 text.split(' ')
3655 .map(parse_capability_token)
3656 .collect::<Result<Vec<_>>>()
3657}
3658
3659pub fn encode_capabilities(capabilities: &[Capability]) -> Result<Vec<u8>> {
3660 let mut out = Vec::new();
3661 for (idx, capability) in capabilities.iter().enumerate() {
3662 validate_capability_field("capability name", &capability.name)?;
3663 if idx != 0 {
3664 out.push(b' ');
3665 }
3666 out.extend_from_slice(capability.name.as_bytes());
3667 if let Some(value) = &capability.value {
3668 validate_capability_field("capability value", value)?;
3669 out.push(b'=');
3670 out.extend_from_slice(value.as_bytes());
3671 }
3672 }
3673 Ok(out)
3674}
3675
3676pub fn parse_ref_advertisement(format: ObjectFormat, payload: &[u8]) -> Result<RefAdvertisement> {
3677 let payload = trim_trailing_lf(payload);
3678 let (reference, capabilities) = match payload.iter().position(|byte| *byte == 0) {
3679 Some(idx) => (&payload[..idx], parse_capabilities(&payload[idx + 1..])?),
3680 None => (payload, Vec::new()),
3681 };
3682 let text =
3683 std::str::from_utf8(reference).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3684 let (oid, name) = text
3685 .split_once(' ')
3686 .ok_or_else(|| GitError::InvalidFormat("advertised ref is missing name".into()))?;
3687 if name.is_empty() {
3688 return Err(GitError::InvalidFormat(
3689 "advertised ref name is empty".into(),
3690 ));
3691 }
3692 Ok(RefAdvertisement {
3693 oid: ObjectId::from_hex(format, oid)?,
3694 name: name.to_string(),
3695 capabilities,
3696 })
3697}
3698
3699pub fn encode_ref_advertisement(advertisement: &RefAdvertisement) -> Result<Vec<u8>> {
3700 validate_protocol_v2_token("advertised ref name", &advertisement.name)?;
3701 let mut out = advertisement.oid.to_string().into_bytes();
3702 out.push(b' ');
3703 out.extend_from_slice(advertisement.name.as_bytes());
3704 if !advertisement.capabilities.is_empty() {
3705 out.push(0);
3706 out.extend_from_slice(&encode_capabilities(&advertisement.capabilities)?);
3707 }
3708 out.push(b'\n');
3709 Ok(out)
3710}
3711
3712pub fn parse_ref_advertisements(
3713 format: ObjectFormat,
3714 frames: &[PktLineFrame],
3715) -> Result<Vec<RefAdvertisement>> {
3716 Ok(parse_ref_advertisement_set(format, frames)?.refs)
3717}
3718
3719pub fn parse_ref_advertisement_set(
3720 format: ObjectFormat,
3721 frames: &[PktLineFrame],
3722) -> Result<RefAdvertisementSet> {
3723 let mut set = RefAdvertisementSet {
3724 protocol: ProtocolVersion::V0,
3725 refs: Vec::new(),
3726 shallow: Vec::new(),
3727 };
3728 let mut saw_flush = false;
3729 let mut in_shallow = false;
3730 for (idx, frame) in frames.iter().enumerate() {
3731 match frame {
3732 PktLineFrame::Data(payload) if !saw_flush => {
3733 let trimmed = trim_trailing_lf(payload);
3734 if trimmed == b"version 1" {
3735 if idx != 0 {
3736 return Err(GitError::InvalidFormat(
3737 "advertised ref protocol version must be the first line".into(),
3738 ));
3739 }
3740 set.protocol = ProtocolVersion::V1;
3741 continue;
3742 }
3743 if trimmed.starts_with(b"version ") {
3744 return Err(GitError::InvalidFormat(
3745 "unsupported advertised ref protocol version".into(),
3746 ));
3747 }
3748 if trimmed.starts_with(b"shallow ") {
3749 if set.refs.is_empty() {
3750 return Err(GitError::InvalidFormat(
3751 "advertised shallow refs must follow advertised refs".into(),
3752 ));
3753 }
3754 let text = parse_protocol_v2_line_text("advertised shallow ref", payload)?;
3755 set.shallow.push(parse_oid_argument(
3756 format,
3757 "advertised shallow ref",
3758 text,
3759 "shallow ",
3760 )?);
3761 in_shallow = true;
3762 continue;
3763 }
3764 if in_shallow {
3765 return Err(GitError::InvalidFormat(
3766 "advertised refs must not follow shallow refs".into(),
3767 ));
3768 }
3769 let advertisement = parse_ref_advertisement(format, payload)?;
3770 if !set.refs.is_empty() && !advertisement.capabilities.is_empty() {
3771 return Err(GitError::InvalidFormat(
3772 "advertised ref capabilities must appear on the first ref".into(),
3773 ));
3774 }
3775 set.refs.push(advertisement);
3776 }
3777 PktLineFrame::Data(_) => {
3778 return Err(GitError::InvalidFormat(
3779 "advertised ref stream has data after flush".into(),
3780 ));
3781 }
3782 PktLineFrame::Flush => {
3783 saw_flush = true;
3784 if idx + 1 != frames.len() {
3785 return Err(GitError::InvalidFormat(
3786 "advertised ref stream has frames after flush".into(),
3787 ));
3788 }
3789 }
3790 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3791 return Err(GitError::InvalidFormat(
3792 "advertised ref stream contains a non-flush control packet".into(),
3793 ));
3794 }
3795 }
3796 }
3797 if !saw_flush {
3798 return Err(GitError::InvalidFormat(
3799 "advertised ref stream missing flush".into(),
3800 ));
3801 }
3802 Ok(set)
3803}
3804
3805pub fn encode_ref_advertisements(advertisements: &[RefAdvertisement]) -> Result<Vec<PktLineFrame>> {
3806 encode_ref_advertisement_set(&RefAdvertisementSet {
3807 protocol: ProtocolVersion::V0,
3808 refs: advertisements.to_vec(),
3809 shallow: Vec::new(),
3810 })
3811}
3812
3813pub fn encode_ref_advertisement_set(set: &RefAdvertisementSet) -> Result<Vec<PktLineFrame>> {
3814 let mut frames = Vec::new();
3815 match set.protocol {
3816 ProtocolVersion::V0 => {}
3817 ProtocolVersion::V1 => frames.push(PktLineFrame::data(line_from_str("version 1"))?),
3818 ProtocolVersion::V2 => {
3819 return Err(GitError::InvalidFormat(
3820 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3821 ));
3822 }
3823 }
3824 if set.refs.is_empty() && !set.shallow.is_empty() {
3825 return Err(GitError::InvalidFormat(
3826 "advertised shallow refs require advertised refs".into(),
3827 ));
3828 }
3829 for (idx, advertisement) in set.refs.iter().enumerate() {
3830 if idx != 0 && !advertisement.capabilities.is_empty() {
3831 return Err(GitError::InvalidFormat(
3832 "advertised ref capabilities must appear on the first ref".into(),
3833 ));
3834 }
3835 frames.push(PktLineFrame::data(encode_ref_advertisement(
3836 advertisement,
3837 )?)?);
3838 }
3839 for oid in &set.shallow {
3840 frames.push(PktLineFrame::data(line_from_str(&format!(
3841 "shallow {oid}"
3842 )))?);
3843 }
3844 frames.push(PktLineFrame::Flush);
3845 Ok(frames)
3846}
3847
3848pub fn read_ref_advertisements(
3849 format: ObjectFormat,
3850 reader: &mut impl Read,
3851) -> Result<Vec<RefAdvertisement>> {
3852 let frames = read_pkt_line_frames_until_flush(reader)?;
3853 parse_ref_advertisements(format, &frames)
3854}
3855
3856pub fn read_ref_advertisement_set(
3857 format: ObjectFormat,
3858 reader: &mut impl Read,
3859) -> Result<RefAdvertisementSet> {
3860 let frames = read_pkt_line_frames_until_flush(reader)?;
3861 parse_ref_advertisement_set(format, &frames)
3862}
3863
3864pub fn write_ref_advertisements(
3865 writer: &mut impl Write,
3866 advertisements: &[RefAdvertisement],
3867) -> Result<()> {
3868 write_ref_advertisement_stream(writer, ProtocolVersion::V0, advertisements, &[])
3869}
3870
3871pub fn write_ref_advertisement_set(
3872 writer: &mut impl Write,
3873 set: &RefAdvertisementSet,
3874) -> Result<()> {
3875 write_ref_advertisement_stream(writer, set.protocol, &set.refs, &set.shallow)
3876}
3877
3878fn write_ref_advertisement_stream(
3879 writer: &mut impl Write,
3880 protocol: ProtocolVersion,
3881 refs: &[RefAdvertisement],
3882 shallow: &[ObjectId],
3883) -> Result<()> {
3884 match protocol {
3885 ProtocolVersion::V0 => {}
3886 ProtocolVersion::V1 => write_pkt_line_payload(writer, b"version 1\n")?,
3887 ProtocolVersion::V2 => {
3888 return Err(GitError::InvalidFormat(
3889 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3890 ));
3891 }
3892 }
3893 if refs.is_empty() && !shallow.is_empty() {
3894 return Err(GitError::InvalidFormat(
3895 "advertised shallow refs require advertised refs".into(),
3896 ));
3897 }
3898 for (idx, advertisement) in refs.iter().enumerate() {
3899 if idx != 0 && !advertisement.capabilities.is_empty() {
3900 return Err(GitError::InvalidFormat(
3901 "advertised ref capabilities must appear on the first ref".into(),
3902 ));
3903 }
3904 write_pkt_line_payload(writer, &encode_ref_advertisement(advertisement)?)?;
3905 }
3906 for oid in shallow {
3907 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
3908 }
3909 writer.write_all(b"0000")?;
3910 Ok(())
3911}
3912
3913pub fn parse_dumb_http_info_refs(
3914 format: ObjectFormat,
3915 input: &[u8],
3916) -> Result<Vec<DumbHttpRefRecord>> {
3917 if input.is_empty() {
3918 return Ok(Vec::new());
3919 }
3920 input
3921 .split_inclusive(|byte| *byte == b'\n')
3922 .map(|line| parse_dumb_http_info_ref_record(format, line))
3923 .collect()
3924}
3925
3926pub fn encode_dumb_http_info_refs(records: &[DumbHttpRefRecord]) -> Result<Vec<u8>> {
3927 let mut out = Vec::new();
3928 for record in records {
3929 validate_dumb_http_ref_name(&record.name)?;
3930 out.extend_from_slice(record.oid.to_string().as_bytes());
3931 out.push(b'\t');
3932 out.extend_from_slice(record.name.as_bytes());
3933 if record.peeled {
3934 out.extend_from_slice(b"^{}");
3935 }
3936 out.push(b'\n');
3937 }
3938 Ok(out)
3939}
3940
3941pub fn read_dumb_http_info_refs(
3942 format: ObjectFormat,
3943 reader: &mut impl Read,
3944) -> Result<Vec<DumbHttpRefRecord>> {
3945 let mut input = Vec::new();
3946 reader.read_to_end(&mut input)?;
3947 parse_dumb_http_info_refs(format, &input)
3948}
3949
3950pub fn write_dumb_http_info_refs(
3951 writer: &mut impl Write,
3952 records: &[DumbHttpRefRecord],
3953) -> Result<()> {
3954 for record in records {
3955 validate_dumb_http_ref_name(&record.name)?;
3956 writer.write_all(record.oid.to_string().as_bytes())?;
3957 writer.write_all(b"\t")?;
3958 writer.write_all(record.name.as_bytes())?;
3959 if record.peeled {
3960 writer.write_all(b"^{}")?;
3961 }
3962 writer.write_all(b"\n")?;
3963 }
3964 Ok(())
3965}
3966
3967pub fn parse_dumb_http_alternates(input: &[u8]) -> Result<Vec<String>> {
3968 if input.is_empty() {
3969 return Ok(Vec::new());
3970 }
3971 input
3972 .split_inclusive(|byte| *byte == b'\n')
3973 .map(parse_dumb_http_alternate)
3974 .collect()
3975}
3976
3977pub fn encode_dumb_http_alternates(alternates: &[String]) -> Result<Vec<u8>> {
3978 let mut out = Vec::new();
3979 for alternate in alternates {
3980 validate_dumb_http_alternate(alternate)?;
3981 out.extend_from_slice(alternate.as_bytes());
3982 out.push(b'\n');
3983 }
3984 Ok(out)
3985}
3986
3987pub fn read_dumb_http_alternates(reader: &mut impl Read) -> Result<Vec<String>> {
3988 let mut input = Vec::new();
3989 reader.read_to_end(&mut input)?;
3990 parse_dumb_http_alternates(&input)
3991}
3992
3993pub fn write_dumb_http_alternates(writer: &mut impl Write, alternates: &[String]) -> Result<()> {
3994 for alternate in alternates {
3995 validate_dumb_http_alternate(alternate)?;
3996 writer.write_all(alternate.as_bytes())?;
3997 writer.write_all(b"\n")?;
3998 }
3999 Ok(())
4000}
4001
4002pub fn parse_dumb_http_packs(
4003 format: ObjectFormat,
4004 input: &[u8],
4005) -> Result<Vec<DumbHttpPackRecord>> {
4006 if input.is_empty() {
4007 return Ok(Vec::new());
4008 }
4009 input
4010 .split_inclusive(|byte| *byte == b'\n')
4011 .map(|line| parse_dumb_http_pack_record(format, line))
4012 .collect()
4013}
4014
4015pub fn encode_dumb_http_packs(records: &[DumbHttpPackRecord]) -> Result<Vec<u8>> {
4016 let mut out = Vec::new();
4017 for record in records {
4018 out.extend_from_slice(format!("P pack-{}.pack\n", record.hash).as_bytes());
4019 }
4020 Ok(out)
4021}
4022
4023pub fn read_dumb_http_packs(
4024 format: ObjectFormat,
4025 reader: &mut impl Read,
4026) -> Result<Vec<DumbHttpPackRecord>> {
4027 let mut input = Vec::new();
4028 reader.read_to_end(&mut input)?;
4029 parse_dumb_http_packs(format, &input)
4030}
4031
4032pub fn write_dumb_http_packs(
4033 writer: &mut impl Write,
4034 records: &[DumbHttpPackRecord],
4035) -> Result<()> {
4036 for record in records {
4037 writer.write_all(format!("P pack-{}.pack\n", record.hash).as_bytes())?;
4038 }
4039 Ok(())
4040}
4041
4042pub fn parse_upload_pack_request(
4043 format: ObjectFormat,
4044 frames: &[PktLineFrame],
4045) -> Result<Option<UploadPackRequest>> {
4046 if matches!(frames, [PktLineFrame::Flush]) {
4047 return Ok(None);
4048 }
4049
4050 let mut request = UploadPackRequest::default();
4051 let mut in_options = false;
4052 let mut saw_flush = false;
4053 for (idx, frame) in frames.iter().enumerate() {
4054 match frame {
4055 PktLineFrame::Data(payload) if !saw_flush => {
4056 let text = parse_protocol_v2_line_text("upload-pack request line", payload)?;
4057 if let Some(value) = text.strip_prefix("want ") {
4058 if in_options {
4059 return Err(GitError::InvalidFormat(
4060 "upload-pack request has want after options".into(),
4061 ));
4062 }
4063 let (oid, capabilities) = if request.wants.is_empty() {
4064 value
4065 .split_once(' ')
4066 .map_or((value, None), |(oid, caps)| (oid, Some(caps.as_bytes())))
4067 } else {
4068 if value.contains(' ') {
4069 return Err(GitError::InvalidFormat(
4070 "additional upload-pack want has capabilities".into(),
4071 ));
4072 }
4073 (value, None)
4074 };
4075 validate_protocol_v2_token("upload-pack want", oid)?;
4076 request.wants.push(ObjectId::from_hex(format, oid)?);
4077 if let Some(capabilities) = capabilities {
4078 request.capabilities = parse_capabilities(capabilities)?;
4079 }
4080 continue;
4081 }
4082
4083 if request.wants.is_empty() {
4084 return Err(GitError::InvalidFormat(
4085 "upload-pack request must start with want".into(),
4086 ));
4087 }
4088 in_options = true;
4089 if text.starts_with("shallow ") {
4090 request.shallow.push(parse_oid_argument(
4091 format,
4092 "upload-pack shallow",
4093 text,
4094 "shallow ",
4095 )?);
4096 } else if text.starts_with("deepen ") {
4097 if request.deepen.is_some() {
4098 return Err(GitError::InvalidFormat(
4099 "upload-pack request has duplicate deepen".into(),
4100 ));
4101 }
4102 request.deepen =
4103 Some(parse_u32_argument("upload-pack deepen", text, "deepen ")?);
4104 } else if text.starts_with("deepen-since ") {
4105 if request.deepen_since.is_some() {
4106 return Err(GitError::InvalidFormat(
4107 "upload-pack request has duplicate deepen-since".into(),
4108 ));
4109 }
4110 request.deepen_since = Some(parse_u64_argument(
4111 "upload-pack deepen-since",
4112 text,
4113 "deepen-since ",
4114 )?);
4115 } else if let Some(name) = text.strip_prefix("deepen-not ") {
4116 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4117 request.deepen_not.push(name.to_string());
4118 } else if let Some(filter) = text.strip_prefix("filter ") {
4119 if request.filter.is_some() {
4120 return Err(GitError::InvalidFormat(
4121 "upload-pack request has duplicate filter".into(),
4122 ));
4123 }
4124 validate_protocol_v2_token("upload-pack filter", filter)?;
4125 request.filter = Some(filter.to_string());
4126 } else {
4127 return Err(GitError::InvalidFormat(format!(
4128 "unsupported upload-pack request line {text}"
4129 )));
4130 }
4131 }
4132 PktLineFrame::Data(_) => {
4133 return Err(GitError::InvalidFormat(
4134 "upload-pack request has data after flush".into(),
4135 ));
4136 }
4137 PktLineFrame::Flush => {
4138 saw_flush = true;
4139 if idx + 1 != frames.len() {
4140 return Err(GitError::InvalidFormat(
4141 "upload-pack request has frames after flush".into(),
4142 ));
4143 }
4144 }
4145 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4146 return Err(GitError::InvalidFormat(
4147 "upload-pack request contains a non-flush control packet".into(),
4148 ));
4149 }
4150 }
4151 }
4152 if !saw_flush {
4153 return Err(GitError::InvalidFormat(
4154 "upload-pack request missing flush".into(),
4155 ));
4156 }
4157 if request.wants.is_empty() {
4158 return Err(GitError::InvalidFormat(
4159 "upload-pack request missing want".into(),
4160 ));
4161 }
4162 Ok(Some(request))
4163}
4164
4165pub fn encode_upload_pack_request(
4166 request: Option<&UploadPackRequest>,
4167) -> Result<Vec<PktLineFrame>> {
4168 let Some(request) = request else {
4169 return Ok(vec![PktLineFrame::Flush]);
4170 };
4171 if request.wants.is_empty() {
4172 return Err(GitError::InvalidFormat(
4173 "upload-pack request missing want".into(),
4174 ));
4175 }
4176
4177 let mut frames = Vec::new();
4178 for (idx, oid) in request.wants.iter().enumerate() {
4179 let mut line = format!("want {oid}");
4180 if idx == 0 && !request.capabilities.is_empty() {
4181 line.push(' ');
4182 line.push_str(
4183 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4184 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4185 );
4186 }
4187 frames.push(PktLineFrame::data(line_from_str(&line))?);
4188 }
4189 for oid in &request.shallow {
4190 frames.push(PktLineFrame::data(line_from_str(&format!(
4191 "shallow {oid}"
4192 )))?);
4193 }
4194 if let Some(deepen) = request.deepen {
4195 if deepen == 0 {
4196 return Err(GitError::InvalidFormat(
4197 "upload-pack deepen must be positive".into(),
4198 ));
4199 }
4200 frames.push(PktLineFrame::data(line_from_str(&format!(
4201 "deepen {deepen}"
4202 )))?);
4203 }
4204 if let Some(deepen_since) = request.deepen_since {
4205 frames.push(PktLineFrame::data(line_from_str(&format!(
4206 "deepen-since {deepen_since}"
4207 )))?);
4208 }
4209 for name in &request.deepen_not {
4210 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4211 frames.push(PktLineFrame::data(line_from_str(&format!(
4212 "deepen-not {name}"
4213 )))?);
4214 }
4215 if let Some(filter) = &request.filter {
4216 validate_protocol_v2_token("upload-pack filter", filter)?;
4217 frames.push(PktLineFrame::data(line_from_str(&format!(
4218 "filter {filter}"
4219 )))?);
4220 }
4221 frames.push(PktLineFrame::Flush);
4222 Ok(frames)
4223}
4224
4225pub fn read_upload_pack_request(
4226 format: ObjectFormat,
4227 reader: &mut impl Read,
4228) -> Result<Option<UploadPackRequest>> {
4229 let frames = read_pkt_line_frames_until_flush(reader)?;
4230 parse_upload_pack_request(format, &frames)
4231}
4232
4233pub fn write_upload_pack_request(
4234 writer: &mut impl Write,
4235 request: Option<&UploadPackRequest>,
4236) -> Result<()> {
4237 let Some(request) = request else {
4238 writer.write_all(b"0000")?;
4239 return Ok(());
4240 };
4241 if request.wants.is_empty() {
4242 return Err(GitError::InvalidFormat(
4243 "upload-pack request missing want".into(),
4244 ));
4245 }
4246
4247 for (idx, oid) in request.wants.iter().enumerate() {
4248 let mut line = format!("want {oid}");
4249 if idx == 0 && !request.capabilities.is_empty() {
4250 line.push(' ');
4251 line.push_str(
4252 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4253 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4254 );
4255 }
4256 write_pkt_line_payload(writer, &line_from_str(&line))?;
4257 }
4258 for oid in &request.shallow {
4259 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
4260 }
4261 if let Some(deepen) = request.deepen {
4262 if deepen == 0 {
4263 return Err(GitError::InvalidFormat(
4264 "upload-pack deepen must be positive".into(),
4265 ));
4266 }
4267 write_pkt_line_payload(writer, &line_from_str(&format!("deepen {deepen}")))?;
4268 }
4269 if let Some(deepen_since) = request.deepen_since {
4270 write_pkt_line_payload(
4271 writer,
4272 &line_from_str(&format!("deepen-since {deepen_since}")),
4273 )?;
4274 }
4275 for name in &request.deepen_not {
4276 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4277 write_pkt_line_payload(writer, &line_from_str(&format!("deepen-not {name}")))?;
4278 }
4279 if let Some(filter) = &request.filter {
4280 validate_protocol_v2_token("upload-pack filter", filter)?;
4281 write_pkt_line_payload(writer, &line_from_str(&format!("filter {filter}")))?;
4282 }
4283 writer.write_all(b"0000")?;
4284 Ok(())
4285}
4286
4287pub fn parse_upload_pack_features(capabilities: &[Capability]) -> Result<UploadPackFeatures> {
4288 let mut features = UploadPackFeatures::default();
4289 for capability in capabilities {
4290 match capability.name.as_str() {
4291 "multi_ack" => set_upload_pack_flag(&mut features.multi_ack, capability)?,
4292 "multi_ack_detailed" => {
4293 set_upload_pack_flag(&mut features.multi_ack_detailed, capability)?
4294 }
4295 "no-done" => set_upload_pack_flag(&mut features.no_done, capability)?,
4296 "thin-pack" => set_upload_pack_flag(&mut features.thin_pack, capability)?,
4297 "side-band" => set_upload_pack_flag(&mut features.side_band, capability)?,
4298 "side-band-64k" => set_upload_pack_flag(&mut features.side_band_64k, capability)?,
4299 "ofs-delta" => set_upload_pack_flag(&mut features.ofs_delta, capability)?,
4300 "shallow" => set_upload_pack_flag(&mut features.shallow, capability)?,
4301 "deepen-since" => set_upload_pack_flag(&mut features.deepen_since, capability)?,
4302 "deepen-not" => set_upload_pack_flag(&mut features.deepen_not, capability)?,
4303 "include-tag" => set_upload_pack_flag(&mut features.include_tag, capability)?,
4304 "no-progress" => set_upload_pack_flag(&mut features.no_progress, capability)?,
4305 "allow-tip-sha1-in-want" => {
4306 set_upload_pack_flag(&mut features.allow_tip_sha1_in_want, capability)?
4307 }
4308 "allow-reachable-sha1-in-want" => {
4309 set_upload_pack_flag(&mut features.allow_reachable_sha1_in_want, capability)?
4310 }
4311 "filter" => set_upload_pack_flag(&mut features.filter, capability)?,
4312 "agent" => {
4313 let Some(agent) = &capability.value else {
4314 return Err(GitError::InvalidFormat(
4315 "upload-pack agent capability is missing value".into(),
4316 ));
4317 };
4318 if features.agent.is_some() {
4319 return Err(GitError::InvalidFormat(
4320 "upload-pack has duplicate agent capability".into(),
4321 ));
4322 }
4323 validate_capability_field("upload-pack agent", agent)?;
4324 features.agent = Some(agent.clone());
4325 }
4326 "object-format" => {
4327 let Some(format) = &capability.value else {
4328 return Err(GitError::InvalidFormat(
4329 "upload-pack object-format capability is missing value".into(),
4330 ));
4331 };
4332 if features.object_format.is_some() {
4333 return Err(GitError::InvalidFormat(
4334 "upload-pack has duplicate object-format capability".into(),
4335 ));
4336 }
4337 validate_capability_field("upload-pack object-format", format)?;
4338 features.object_format = Some(format.parse()?);
4339 }
4340 "symref" => {
4341 let Some(symref) = &capability.value else {
4342 continue;
4343 };
4344 validate_capability_field("upload-pack symref", symref)?;
4345 features.symrefs.push(symref.clone());
4346 }
4347 _ => {
4348 encode_capabilities(std::slice::from_ref(capability))?;
4349 if features
4350 .unknown
4351 .iter()
4352 .any(|known| known.name == capability.name)
4353 {
4354 return Err(GitError::InvalidFormat(format!(
4355 "upload-pack has duplicate {} capability",
4356 capability.name
4357 )));
4358 }
4359 features.unknown.push(capability.clone());
4360 }
4361 }
4362 }
4363 Ok(features)
4364}
4365
4366pub fn encode_upload_pack_features(features: &UploadPackFeatures) -> Result<Vec<Capability>> {
4367 let mut capabilities = Vec::new();
4368 push_upload_pack_flag(&mut capabilities, "multi_ack", features.multi_ack);
4369 push_upload_pack_flag(
4370 &mut capabilities,
4371 "multi_ack_detailed",
4372 features.multi_ack_detailed,
4373 );
4374 push_upload_pack_flag(&mut capabilities, "no-done", features.no_done);
4375 push_upload_pack_flag(&mut capabilities, "thin-pack", features.thin_pack);
4376 push_upload_pack_flag(&mut capabilities, "side-band", features.side_band);
4377 push_upload_pack_flag(&mut capabilities, "side-band-64k", features.side_band_64k);
4378 push_upload_pack_flag(&mut capabilities, "ofs-delta", features.ofs_delta);
4379 push_upload_pack_flag(&mut capabilities, "shallow", features.shallow);
4380 push_upload_pack_flag(&mut capabilities, "deepen-since", features.deepen_since);
4381 push_upload_pack_flag(&mut capabilities, "deepen-not", features.deepen_not);
4382 push_upload_pack_flag(&mut capabilities, "include-tag", features.include_tag);
4383 push_upload_pack_flag(&mut capabilities, "no-progress", features.no_progress);
4384 push_upload_pack_flag(
4385 &mut capabilities,
4386 "allow-tip-sha1-in-want",
4387 features.allow_tip_sha1_in_want,
4388 );
4389 push_upload_pack_flag(
4390 &mut capabilities,
4391 "allow-reachable-sha1-in-want",
4392 features.allow_reachable_sha1_in_want,
4393 );
4394 push_upload_pack_flag(&mut capabilities, "filter", features.filter);
4395 if let Some(agent) = &features.agent {
4396 validate_capability_field("upload-pack agent", agent)?;
4397 capabilities.push(Capability {
4398 name: "agent".into(),
4399 value: Some(agent.clone()),
4400 });
4401 }
4402 if let Some(format) = features.object_format {
4403 capabilities.push(Capability {
4404 name: "object-format".into(),
4405 value: Some(format.name().into()),
4406 });
4407 }
4408 for symref in &features.symrefs {
4409 validate_capability_field("upload-pack symref", symref)?;
4410 capabilities.push(Capability {
4411 name: "symref".into(),
4412 value: Some(symref.clone()),
4413 });
4414 }
4415 for capability in &features.unknown {
4416 if is_known_upload_pack_capability(&capability.name) {
4417 return Err(GitError::InvalidFormat(format!(
4418 "upload-pack unknown capability duplicates known capability {}",
4419 capability.name
4420 )));
4421 }
4422 encode_capabilities(std::slice::from_ref(capability))?;
4423 capabilities.push(capability.clone());
4424 }
4425 Ok(capabilities)
4426}
4427
4428pub fn validate_upload_pack_request_features(
4429 features: &UploadPackFeatures,
4430 request: &UploadPackRequest,
4431) -> Result<()> {
4432 for capability in &request.capabilities {
4433 if is_upload_pack_flag_capability(&capability.name) {
4434 reject_capability_value("upload-pack request capability", capability)?;
4435 }
4436 match capability.name.as_str() {
4437 "multi_ack" if !features.multi_ack => {
4438 return Err(GitError::InvalidFormat(
4439 "upload-pack request uses multi_ack without advertised capability".into(),
4440 ));
4441 }
4442 "multi_ack_detailed" if !features.multi_ack_detailed => {
4443 return Err(GitError::InvalidFormat(
4444 "upload-pack request uses multi_ack_detailed without advertised capability"
4445 .into(),
4446 ));
4447 }
4448 "no-done" if !features.no_done => {
4449 return Err(GitError::InvalidFormat(
4450 "upload-pack request uses no-done without advertised capability".into(),
4451 ));
4452 }
4453 "thin-pack" if !features.thin_pack => {
4454 return Err(GitError::InvalidFormat(
4455 "upload-pack request uses thin-pack without advertised capability".into(),
4456 ));
4457 }
4458 "side-band" if !features.side_band => {
4459 return Err(GitError::InvalidFormat(
4460 "upload-pack request uses side-band without advertised capability".into(),
4461 ));
4462 }
4463 "side-band-64k" if !features.side_band_64k => {
4464 return Err(GitError::InvalidFormat(
4465 "upload-pack request uses side-band-64k without advertised capability".into(),
4466 ));
4467 }
4468 "ofs-delta" if !features.ofs_delta => {
4469 return Err(GitError::InvalidFormat(
4470 "upload-pack request uses ofs-delta without advertised capability".into(),
4471 ));
4472 }
4473 "include-tag" if !features.include_tag => {
4474 return Err(GitError::InvalidFormat(
4475 "upload-pack request uses include-tag without advertised capability".into(),
4476 ));
4477 }
4478 "no-progress" if !features.no_progress => {
4479 return Err(GitError::InvalidFormat(
4480 "upload-pack request uses no-progress without advertised capability".into(),
4481 ));
4482 }
4483 "allow-tip-sha1-in-want" if !features.allow_tip_sha1_in_want => {
4484 return Err(GitError::InvalidFormat(
4485 "upload-pack request uses allow-tip-sha1-in-want without advertised capability"
4486 .into(),
4487 ));
4488 }
4489 "allow-reachable-sha1-in-want" if !features.allow_reachable_sha1_in_want => {
4490 return Err(GitError::InvalidFormat(
4491 "upload-pack request uses allow-reachable-sha1-in-want without advertised capability"
4492 .into(),
4493 ));
4494 }
4495 "filter" if !features.filter => {
4496 return Err(GitError::InvalidFormat(
4497 "upload-pack request uses filter capability without advertised capability"
4498 .into(),
4499 ));
4500 }
4501 "agent" => {
4502 let Some(agent) = &capability.value else {
4503 return Err(GitError::InvalidFormat(
4504 "upload-pack request agent capability is missing value".into(),
4505 ));
4506 };
4507 validate_capability_field("upload-pack request agent", agent)?;
4508 }
4509 "object-format" => {
4510 let Some(format) = &capability.value else {
4511 return Err(GitError::InvalidFormat(
4512 "upload-pack request object-format capability is missing value".into(),
4513 ));
4514 };
4515 let requested_format: ObjectFormat = format.parse()?;
4516 if features.object_format != Some(requested_format) {
4517 return Err(GitError::InvalidFormat(
4518 "upload-pack request object-format was not advertised".into(),
4519 ));
4520 }
4521 }
4522 name if is_known_upload_pack_capability(name) => {}
4523 _ => {
4524 if !features
4525 .unknown
4526 .iter()
4527 .any(|advertised| advertised.name == capability.name)
4528 {
4529 return Err(GitError::InvalidFormat(format!(
4530 "upload-pack request uses unadvertised capability {}",
4531 capability.name
4532 )));
4533 }
4534 }
4535 }
4536 }
4537
4538 let sideband = request
4539 .capabilities
4540 .iter()
4541 .any(|capability| capability.name == "side-band");
4542 let sideband_64k = request
4543 .capabilities
4544 .iter()
4545 .any(|capability| capability.name == "side-band-64k");
4546 if sideband && sideband_64k {
4547 return Err(GitError::InvalidFormat(
4548 "upload-pack request must not request both side-band and side-band-64k".into(),
4549 ));
4550 }
4551
4552 if !features.shallow && (!request.shallow.is_empty() || request.deepen.is_some()) {
4553 return Err(GitError::InvalidFormat(
4554 "upload-pack request uses shallow/deepen without advertised shallow capability".into(),
4555 ));
4556 }
4557 if !features.deepen_since && request.deepen_since.is_some() {
4558 return Err(GitError::InvalidFormat(
4559 "upload-pack request uses deepen-since without advertised capability".into(),
4560 ));
4561 }
4562 if !features.deepen_not && !request.deepen_not.is_empty() {
4563 return Err(GitError::InvalidFormat(
4564 "upload-pack request uses deepen-not without advertised capability".into(),
4565 ));
4566 }
4567 if !features.filter && request.filter.is_some() {
4568 return Err(GitError::InvalidFormat(
4569 "upload-pack request uses filter without advertised capability".into(),
4570 ));
4571 }
4572 Ok(())
4573}
4574
4575pub fn build_upload_pack_raw_packfile_response<C, B>(
4576 features: &UploadPackFeatures,
4577 request: UploadPackRequest,
4578 haves: impl IntoIterator<Item = ObjectId>,
4579 mut contains_object: C,
4580 mut build_pack: B,
4581) -> Result<UploadPackRawPackfileResponse>
4582where
4583 C: FnMut(&ObjectId) -> Result<bool>,
4584 B: FnMut(Vec<ObjectId>, Vec<ObjectId>) -> Result<Option<Vec<u8>>>,
4585{
4586 validate_upload_pack_request_features(features, &request)?;
4587 for want in &request.wants {
4588 if !contains_object(want)? {
4589 return Err(GitError::InvalidObject(format!(
4590 "upload-pack requested missing object {want}"
4591 )));
4592 }
4593 }
4594 let known_haves = haves
4595 .into_iter()
4596 .filter_map(|oid| match contains_object(&oid) {
4597 Ok(true) => Some(Ok(oid)),
4598 Ok(false) => None,
4599 Err(err) => Some(Err(err)),
4600 })
4601 .collect::<Result<Vec<_>>>()?;
4602 let packfile = build_pack(request.wants, known_haves)?
4603 .ok_or_else(|| GitError::InvalidObject("upload-pack request produced empty pack".into()))?;
4604 Ok(UploadPackRawPackfileResponse {
4605 acknowledgments: vec![UploadPackAcknowledgment::Nak],
4606 packfile,
4607 })
4608}
4609
4610pub fn parse_upload_pack_shallow_update(
4611 format: ObjectFormat,
4612 frames: &[PktLineFrame],
4613) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4614 let mut entries = Vec::new();
4615 let mut saw_flush = false;
4616 for (idx, frame) in frames.iter().enumerate() {
4617 match frame {
4618 PktLineFrame::Data(payload) if !saw_flush => {
4619 entries.push(parse_fetch_shallow_info(format, payload)?);
4620 }
4621 PktLineFrame::Data(_) => {
4622 return Err(GitError::InvalidFormat(
4623 "upload-pack shallow update has data after flush".into(),
4624 ));
4625 }
4626 PktLineFrame::Flush => {
4627 saw_flush = true;
4628 if idx + 1 != frames.len() {
4629 return Err(GitError::InvalidFormat(
4630 "upload-pack shallow update has frames after flush".into(),
4631 ));
4632 }
4633 }
4634 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4635 return Err(GitError::InvalidFormat(
4636 "upload-pack shallow update contains a non-flush control packet".into(),
4637 ));
4638 }
4639 }
4640 }
4641 if !saw_flush {
4642 return Err(GitError::InvalidFormat(
4643 "upload-pack shallow update missing flush".into(),
4644 ));
4645 }
4646 Ok(entries)
4647}
4648
4649pub fn encode_upload_pack_shallow_update(
4650 entries: &[ProtocolV2FetchShallowInfo],
4651) -> Result<Vec<PktLineFrame>> {
4652 let mut frames = Vec::new();
4653 for entry in entries {
4654 let line = match entry {
4655 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4656 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4657 };
4658 frames.push(PktLineFrame::data(line_from_str(&line))?);
4659 }
4660 frames.push(PktLineFrame::Flush);
4661 Ok(frames)
4662}
4663
4664pub fn read_upload_pack_shallow_update(
4665 format: ObjectFormat,
4666 reader: &mut impl Read,
4667) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4668 let frames = read_pkt_line_frames_until_flush(reader)?;
4669 parse_upload_pack_shallow_update(format, &frames)
4670}
4671
4672pub fn write_upload_pack_shallow_update(
4673 writer: &mut impl Write,
4674 entries: &[ProtocolV2FetchShallowInfo],
4675) -> Result<()> {
4676 for entry in entries {
4677 let line = match entry {
4678 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4679 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4680 };
4681 write_pkt_line_payload(writer, &line_from_str(&line))?;
4682 }
4683 writer.write_all(b"0000")?;
4684 Ok(())
4685}
4686
4687pub fn parse_upload_pack_negotiation_request(
4688 format: ObjectFormat,
4689 frames: &[PktLineFrame],
4690) -> Result<UploadPackNegotiationRequest> {
4691 let mut request = UploadPackNegotiationRequest::default();
4692 let mut terminated = false;
4693 for (idx, frame) in frames.iter().enumerate() {
4694 match frame {
4695 PktLineFrame::Data(payload) if !terminated => {
4696 let text = parse_protocol_v2_line_text("upload-pack negotiation line", payload)?;
4697 if text == "done" {
4698 request.done = true;
4699 terminated = true;
4700 if idx + 1 != frames.len() {
4701 return Err(GitError::InvalidFormat(
4702 "upload-pack negotiation has frames after done".into(),
4703 ));
4704 }
4705 } else if text.starts_with("have ") {
4706 request.haves.push(parse_oid_argument(
4707 format,
4708 "upload-pack have",
4709 text,
4710 "have ",
4711 )?);
4712 } else {
4713 return Err(GitError::InvalidFormat(format!(
4714 "unsupported upload-pack negotiation line {text}"
4715 )));
4716 }
4717 }
4718 PktLineFrame::Data(_) => {
4719 return Err(GitError::InvalidFormat(
4720 "upload-pack negotiation has data after terminator".into(),
4721 ));
4722 }
4723 PktLineFrame::Flush => {
4724 terminated = true;
4725 if idx + 1 != frames.len() {
4726 return Err(GitError::InvalidFormat(
4727 "upload-pack negotiation has frames after flush".into(),
4728 ));
4729 }
4730 }
4731 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4732 return Err(GitError::InvalidFormat(
4733 "upload-pack negotiation contains a non-flush control packet".into(),
4734 ));
4735 }
4736 }
4737 }
4738 if !terminated {
4739 return Err(GitError::InvalidFormat(
4740 "upload-pack negotiation missing terminator".into(),
4741 ));
4742 }
4743 Ok(request)
4744}
4745
4746pub fn encode_upload_pack_negotiation_request(
4747 request: &UploadPackNegotiationRequest,
4748) -> Result<Vec<PktLineFrame>> {
4749 let mut frames = Vec::new();
4750 for oid in &request.haves {
4751 frames.push(PktLineFrame::data(line_from_str(&format!("have {oid}")))?);
4752 }
4753 if request.done {
4754 frames.push(PktLineFrame::data(line_from_str("done"))?);
4755 } else {
4756 frames.push(PktLineFrame::Flush);
4757 }
4758 Ok(frames)
4759}
4760
4761pub fn read_upload_pack_negotiation_request(
4762 format: ObjectFormat,
4763 reader: &mut impl Read,
4764) -> Result<UploadPackNegotiationRequest> {
4765 let mut frames = Vec::new();
4766 loop {
4767 let Some(frame) = read_pkt_line_frame(reader)? else {
4768 return Err(GitError::InvalidFormat(
4769 "pkt-line stream ended before upload-pack negotiation terminator".into(),
4770 ));
4771 };
4772 let done = match &frame {
4773 PktLineFrame::Flush => true,
4774 PktLineFrame::Data(payload) => trim_trailing_lf(payload) == b"done",
4775 _ => false,
4776 };
4777 frames.push(frame);
4778 if done {
4779 return parse_upload_pack_negotiation_request(format, &frames);
4780 }
4781 }
4782}
4783
4784pub fn write_upload_pack_negotiation_request(
4785 writer: &mut impl Write,
4786 request: &UploadPackNegotiationRequest,
4787) -> Result<()> {
4788 for oid in &request.haves {
4789 write_pkt_line_payload(writer, &line_from_str(&format!("have {oid}")))?;
4790 }
4791 if request.done {
4792 write_pkt_line_payload(writer, b"done\n")?;
4793 } else {
4794 writer.write_all(b"0000")?;
4795 }
4796 Ok(())
4797}
4798
4799pub fn parse_upload_pack_acknowledgment(
4800 format: ObjectFormat,
4801 payload: &[u8],
4802) -> Result<UploadPackAcknowledgment> {
4803 let text = parse_protocol_v2_line_text("upload-pack acknowledgment", payload)?;
4804 if text == "NAK" {
4805 return Ok(UploadPackAcknowledgment::Nak);
4806 }
4807 let Some(rest) = text.strip_prefix("ACK ") else {
4808 return Err(GitError::InvalidFormat(format!(
4809 "unsupported upload-pack acknowledgment {text}"
4810 )));
4811 };
4812 let mut fields = rest.split(' ');
4813 let oid = fields
4814 .next()
4815 .ok_or_else(|| GitError::InvalidFormat("upload-pack ACK missing object id".into()))?;
4816 validate_protocol_v2_token("upload-pack ACK", oid)?;
4817 let status = match fields.next() {
4818 None => None,
4819 Some("continue") => Some(UploadPackAckStatus::Continue),
4820 Some("common") => Some(UploadPackAckStatus::Common),
4821 Some("ready") => Some(UploadPackAckStatus::Ready),
4822 Some(other) => {
4823 return Err(GitError::InvalidFormat(format!(
4824 "unsupported upload-pack ACK status {other}"
4825 )));
4826 }
4827 };
4828 if fields.next().is_some() {
4829 return Err(GitError::InvalidFormat(
4830 "upload-pack ACK has too many fields".into(),
4831 ));
4832 }
4833 Ok(UploadPackAcknowledgment::Ack {
4834 oid: ObjectId::from_hex(format, oid)?,
4835 status,
4836 })
4837}
4838
4839pub fn encode_upload_pack_acknowledgment(
4840 acknowledgment: &UploadPackAcknowledgment,
4841) -> Result<Vec<u8>> {
4842 let line = match acknowledgment {
4843 UploadPackAcknowledgment::Nak => "NAK".to_string(),
4844 UploadPackAcknowledgment::Ack { oid, status } => {
4845 let mut line = format!("ACK {oid}");
4846 if let Some(status) = status {
4847 line.push(' ');
4848 line.push_str(match status {
4849 UploadPackAckStatus::Continue => "continue",
4850 UploadPackAckStatus::Common => "common",
4851 UploadPackAckStatus::Ready => "ready",
4852 });
4853 }
4854 line
4855 }
4856 };
4857 Ok(line_from_str(&line))
4858}
4859
4860pub fn read_upload_pack_acknowledgment(
4861 format: ObjectFormat,
4862 reader: &mut impl Read,
4863) -> Result<UploadPackAcknowledgment> {
4864 let Some(frame) = read_pkt_line_frame(reader)? else {
4865 return Err(GitError::InvalidFormat(
4866 "pkt-line stream ended before upload-pack acknowledgment".into(),
4867 ));
4868 };
4869 match frame {
4870 PktLineFrame::Data(payload) => parse_upload_pack_acknowledgment(format, &payload),
4871 _ => Err(GitError::InvalidFormat(
4872 "upload-pack acknowledgment must be a data packet".into(),
4873 )),
4874 }
4875}
4876
4877pub fn write_upload_pack_acknowledgment(
4878 writer: &mut impl Write,
4879 acknowledgment: &UploadPackAcknowledgment,
4880) -> Result<()> {
4881 write_pkt_line_payload(writer, &encode_upload_pack_acknowledgment(acknowledgment)?)
4882}
4883
4884pub fn parse_upload_pack_packfile_response(
4885 format: ObjectFormat,
4886 frames: &[PktLineFrame],
4887) -> Result<UploadPackPackfileResponse> {
4888 let mut response = UploadPackPackfileResponse::default();
4889 let mut in_sideband = false;
4890 let mut saw_flush = false;
4891 for (idx, frame) in frames.iter().enumerate() {
4892 match frame {
4893 PktLineFrame::Data(payload) if !saw_flush => {
4894 if !in_sideband
4895 && (trim_trailing_lf(payload) == b"NAK" || payload.starts_with(b"ACK "))
4896 {
4897 response
4898 .acknowledgments
4899 .push(parse_upload_pack_acknowledgment(format, payload)?);
4900 continue;
4901 }
4902 in_sideband = true;
4903 response.sideband.push(parse_sideband_packet(payload)?);
4904 }
4905 PktLineFrame::Data(_) => {
4906 return Err(GitError::InvalidFormat(
4907 "upload-pack packfile response has data after flush".into(),
4908 ));
4909 }
4910 PktLineFrame::Flush => {
4911 saw_flush = true;
4912 if idx + 1 != frames.len() {
4913 return Err(GitError::InvalidFormat(
4914 "upload-pack packfile response has frames after flush".into(),
4915 ));
4916 }
4917 }
4918 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4919 return Err(GitError::InvalidFormat(
4920 "upload-pack packfile response contains a non-flush control packet".into(),
4921 ));
4922 }
4923 }
4924 }
4925 if !saw_flush {
4926 return Err(GitError::InvalidFormat(
4927 "upload-pack packfile response missing flush".into(),
4928 ));
4929 }
4930 Ok(response)
4931}
4932
4933pub fn encode_upload_pack_packfile_response(
4934 response: &UploadPackPackfileResponse,
4935) -> Result<Vec<PktLineFrame>> {
4936 let mut frames = Vec::new();
4937 for acknowledgment in &response.acknowledgments {
4938 frames.push(PktLineFrame::data(encode_upload_pack_acknowledgment(
4939 acknowledgment,
4940 )?)?);
4941 }
4942 for packet in &response.sideband {
4943 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
4944 }
4945 frames.push(PktLineFrame::Flush);
4946 Ok(frames)
4947}
4948
4949pub fn read_upload_pack_packfile_response(
4950 format: ObjectFormat,
4951 reader: &mut impl Read,
4952) -> Result<UploadPackPackfileResponse> {
4953 let frames = read_pkt_line_frames_until_flush(reader)?;
4954 parse_upload_pack_packfile_response(format, &frames)
4955}
4956
4957pub fn write_upload_pack_packfile_response(
4958 writer: &mut impl Write,
4959 response: &UploadPackPackfileResponse,
4960) -> Result<()> {
4961 for acknowledgment in &response.acknowledgments {
4962 write_upload_pack_acknowledgment(writer, acknowledgment)?;
4963 }
4964 for packet in &response.sideband {
4965 write_sideband_packet(writer, packet)?;
4966 }
4967 writer.write_all(b"0000")?;
4968 Ok(())
4969}
4970
4971pub fn demux_upload_pack_packfile_response(
4972 response: &UploadPackPackfileResponse,
4973) -> Result<SideBandDemux> {
4974 demux_sideband_packets(&response.sideband)
4975}
4976
4977pub fn parse_upload_pack_raw_packfile_response(
4978 format: ObjectFormat,
4979 input: &[u8],
4980) -> Result<UploadPackRawPackfileResponse> {
4981 let mut response = UploadPackRawPackfileResponse::default();
4982 let mut offset = 0usize;
4983 while offset < input.len() {
4984 match PktLineFrame::parse(&input[offset..]) {
4985 Ok((PktLineFrame::Data(payload), consumed)) => {
4986 let trimmed = trim_trailing_lf(&payload);
4987 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
4988 response
4989 .acknowledgments
4990 .push(parse_upload_pack_acknowledgment(format, &payload)?);
4991 offset += consumed;
4992 continue;
4993 }
4994 return Err(GitError::InvalidFormat(
4995 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
4996 ));
4997 }
4998 Ok((PktLineFrame::Flush | PktLineFrame::Delimiter | PktLineFrame::ResponseEnd, _)) => {
4999 return Err(GitError::InvalidFormat(
5000 "upload-pack raw packfile response contains a control packet".into(),
5001 ));
5002 }
5003 Err(_) if input[offset..].starts_with(b"PACK") => break,
5004 Err(err) => return Err(err),
5005 }
5006 }
5007 response.packfile = input[offset..].to_vec();
5008 if response.packfile.is_empty() {
5009 return Err(GitError::InvalidFormat(
5010 "upload-pack raw packfile response missing packfile".into(),
5011 ));
5012 }
5013 if !response.packfile.starts_with(b"PACK") {
5014 return Err(GitError::InvalidFormat(
5015 "upload-pack raw packfile response packfile must start with PACK".into(),
5016 ));
5017 }
5018 Ok(response)
5019}
5020
5021pub fn encode_upload_pack_raw_packfile_response(
5022 response: &UploadPackRawPackfileResponse,
5023) -> Result<Vec<u8>> {
5024 if response.packfile.is_empty() {
5025 return Err(GitError::InvalidFormat(
5026 "upload-pack raw packfile response missing packfile".into(),
5027 ));
5028 }
5029 if !response.packfile.starts_with(b"PACK") {
5030 return Err(GitError::InvalidFormat(
5031 "upload-pack raw packfile response packfile must start with PACK".into(),
5032 ));
5033 }
5034 let mut out = Vec::new();
5035 for acknowledgment in &response.acknowledgments {
5036 write_pkt_line_payload(
5037 &mut out,
5038 &encode_upload_pack_acknowledgment(acknowledgment)?,
5039 )?;
5040 }
5041 out.extend_from_slice(&response.packfile);
5042 Ok(out)
5043}
5044
5045pub fn read_upload_pack_raw_packfile_response(
5046 format: ObjectFormat,
5047 reader: &mut impl Read,
5048) -> Result<UploadPackRawPackfileResponse> {
5049 let mut input = Vec::new();
5050 reader.read_to_end(&mut input)?;
5051 parse_upload_pack_raw_packfile_response(format, &input)
5052}
5053
5054pub fn write_upload_pack_raw_packfile_response(
5055 writer: &mut impl Write,
5056 response: &UploadPackRawPackfileResponse,
5057) -> Result<()> {
5058 if response.packfile.is_empty() {
5059 return Err(GitError::InvalidFormat(
5060 "upload-pack raw packfile response missing packfile".into(),
5061 ));
5062 }
5063 if !response.packfile.starts_with(b"PACK") {
5064 return Err(GitError::InvalidFormat(
5065 "upload-pack raw packfile response packfile must start with PACK".into(),
5066 ));
5067 }
5068 for acknowledgment in &response.acknowledgments {
5069 write_upload_pack_acknowledgment(writer, acknowledgment)?;
5070 }
5071 writer.write_all(&response.packfile)?;
5072 Ok(())
5073}
5074
5075pub fn parse_upload_pack_shallow_info_section(
5086 format: ObjectFormat,
5087 input: &[u8],
5088) -> Result<(Vec<ProtocolV2FetchShallowInfo>, usize)> {
5089 let mut entries = Vec::new();
5090 let mut offset = 0usize;
5091 loop {
5092 let (frame, consumed) = PktLineFrame::parse(&input[offset..])?;
5093 offset += consumed;
5094 match frame {
5095 PktLineFrame::Data(payload) => {
5096 entries.push(parse_fetch_shallow_info(format, &payload)?)
5097 }
5098 PktLineFrame::Flush => return Ok((entries, offset)),
5099 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5100 return Err(GitError::InvalidFormat(
5101 "upload-pack shallow-info section contains a non-flush control packet".into(),
5102 ));
5103 }
5104 }
5105 }
5106}
5107
5108pub fn parse_upload_pack_shallow_info_and_raw_packfile_response(
5119 format: ObjectFormat,
5120 input: &[u8],
5121) -> Result<(
5122 Vec<ProtocolV2FetchShallowInfo>,
5123 UploadPackRawPackfileResponse,
5124)> {
5125 let (shallow, consumed) = parse_upload_pack_shallow_info_section(format, input)?;
5126 let response = parse_upload_pack_raw_packfile_response(format, &input[consumed..])?;
5127 Ok((shallow, response))
5128}
5129
5130pub fn read_upload_pack_shallow_info_and_raw_packfile_response(
5138 format: ObjectFormat,
5139 reader: &mut impl Read,
5140) -> Result<(
5141 Vec<ProtocolV2FetchShallowInfo>,
5142 UploadPackRawPackfileResponse,
5143)> {
5144 let mut input = Vec::new();
5145 reader.read_to_end(&mut input)?;
5146 parse_upload_pack_shallow_info_and_raw_packfile_response(format, &input)
5147}
5148
5149pub fn parse_receive_pack_request(
5150 format: ObjectFormat,
5151 frames: &[PktLineFrame],
5152) -> Result<ReceivePackRequest> {
5153 let mut request = ReceivePackRequest::default();
5154 let mut saw_command = false;
5155 let mut saw_flush = false;
5156 for (idx, frame) in frames.iter().enumerate() {
5157 match frame {
5158 PktLineFrame::Data(payload) if !saw_flush => {
5159 let payload = trim_trailing_lf(payload);
5160 if payload.is_empty() {
5161 return Err(GitError::InvalidFormat(
5162 "receive-pack request line is empty".into(),
5163 ));
5164 }
5165 if let Some(shallow) = payload.strip_prefix(b"shallow ") {
5166 if saw_command {
5167 return Err(GitError::InvalidFormat(
5168 "receive-pack request has shallow after commands".into(),
5169 ));
5170 }
5171 let shallow = std::str::from_utf8(shallow)
5172 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
5173 validate_protocol_v2_token("receive-pack shallow", shallow)?;
5174 request.shallow.push(ObjectId::from_hex(format, shallow)?);
5175 continue;
5176 }
5177
5178 let (command, capabilities) = match payload.iter().position(|byte| *byte == 0) {
5179 Some(nul) if !saw_command => (
5180 &payload[..nul],
5181 Some(parse_capabilities(&payload[nul + 1..])?),
5182 ),
5183 Some(_) => {
5184 return Err(GitError::InvalidFormat(
5185 "receive-pack capabilities must appear on the first command".into(),
5186 ));
5187 }
5188 None => (payload, None),
5189 };
5190 let command = parse_receive_pack_command(format, command)?;
5191 if let Some(capabilities) = capabilities {
5192 request.capabilities = capabilities;
5193 }
5194 request.commands.push(command);
5195 saw_command = true;
5196 }
5197 PktLineFrame::Data(_) => {
5198 return Err(GitError::InvalidFormat(
5199 "receive-pack request has data after flush".into(),
5200 ));
5201 }
5202 PktLineFrame::Flush => {
5203 saw_flush = true;
5204 if idx + 1 != frames.len() {
5205 return Err(GitError::InvalidFormat(
5206 "receive-pack request has frames after flush".into(),
5207 ));
5208 }
5209 }
5210 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5211 return Err(GitError::InvalidFormat(
5212 "receive-pack request contains a non-flush control packet".into(),
5213 ));
5214 }
5215 }
5216 }
5217 if !saw_flush {
5218 return Err(GitError::InvalidFormat(
5219 "receive-pack request missing flush".into(),
5220 ));
5221 }
5222 if !request.shallow.is_empty() && request.commands.is_empty() {
5223 return Err(GitError::InvalidFormat(
5224 "receive-pack request has shallow lines without commands".into(),
5225 ));
5226 }
5227 Ok(request)
5228}
5229
5230pub fn encode_receive_pack_request(request: &ReceivePackRequest) -> Result<Vec<PktLineFrame>> {
5231 if !request.shallow.is_empty() && request.commands.is_empty() {
5232 return Err(GitError::InvalidFormat(
5233 "receive-pack request has shallow lines without commands".into(),
5234 ));
5235 }
5236
5237 let mut frames = Vec::new();
5238 for oid in &request.shallow {
5239 frames.push(PktLineFrame::data(line_from_str(&format!(
5240 "shallow {oid}"
5241 )))?);
5242 }
5243 for (idx, command) in request.commands.iter().enumerate() {
5244 let mut payload = format_receive_pack_command(command)?;
5245 if idx == 0 && !request.capabilities.is_empty() {
5246 payload.push(0);
5247 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5248 }
5249 payload.push(b'\n');
5250 frames.push(PktLineFrame::data(payload)?);
5251 }
5252 frames.push(PktLineFrame::Flush);
5253 Ok(frames)
5254}
5255
5256pub fn read_receive_pack_request(
5257 format: ObjectFormat,
5258 reader: &mut impl Read,
5259) -> Result<ReceivePackRequest> {
5260 let frames = read_pkt_line_frames_until_flush(reader)?;
5261 parse_receive_pack_request(format, &frames)
5262}
5263
5264pub fn write_receive_pack_request(
5265 writer: &mut impl Write,
5266 request: &ReceivePackRequest,
5267) -> Result<()> {
5268 if !request.shallow.is_empty() && request.commands.is_empty() {
5269 return Err(GitError::InvalidFormat(
5270 "receive-pack request has shallow lines without commands".into(),
5271 ));
5272 }
5273
5274 for oid in &request.shallow {
5275 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
5276 }
5277 for (idx, command) in request.commands.iter().enumerate() {
5278 let mut payload = format_receive_pack_command(command)?;
5279 if idx == 0 && !request.capabilities.is_empty() {
5280 payload.push(0);
5281 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5282 }
5283 payload.push(b'\n');
5284 write_pkt_line_payload(writer, &payload)?;
5285 }
5286 writer.write_all(b"0000")?;
5287 Ok(())
5288}
5289
5290pub fn parse_receive_pack_push_request(
5291 format: ObjectFormat,
5292 input: &[u8],
5293 has_push_options: bool,
5294) -> Result<ReceivePackPushRequest> {
5295 let (command_frames, consumed) = parse_pkt_line_frames_until_flush_from(input)?;
5296 let commands = parse_receive_pack_request(format, &command_frames)?;
5297 let mut offset = consumed;
5298 let push_options = if has_push_options {
5299 let (push_option_frames, consumed) =
5300 parse_pkt_line_frames_until_flush_from(&input[offset..])?;
5301 offset += consumed;
5302 Some(parse_receive_pack_push_options(&push_option_frames)?)
5303 } else {
5304 None
5305 };
5306 Ok(ReceivePackPushRequest {
5307 commands,
5308 push_options,
5309 packfile: input[offset..].to_vec(),
5310 })
5311}
5312
5313pub fn encode_receive_pack_push_request(request: &ReceivePackPushRequest) -> Result<Vec<u8>> {
5314 let mut out = Vec::new();
5315 write_receive_pack_request(&mut out, &request.commands)?;
5316 if let Some(push_options) = &request.push_options {
5317 write_receive_pack_push_options(&mut out, push_options)?;
5318 }
5319 out.extend_from_slice(&request.packfile);
5320 Ok(out)
5321}
5322
5323pub fn read_receive_pack_push_request(
5324 format: ObjectFormat,
5325 reader: &mut impl Read,
5326 has_push_options: bool,
5327) -> Result<ReceivePackPushRequest> {
5328 let commands = read_receive_pack_request(format, reader)?;
5329 let push_options = if has_push_options {
5330 Some(read_receive_pack_push_options(reader)?)
5331 } else {
5332 None
5333 };
5334 let mut packfile = Vec::new();
5335 reader.read_to_end(&mut packfile)?;
5336 Ok(ReceivePackPushRequest {
5337 commands,
5338 push_options,
5339 packfile,
5340 })
5341}
5342
5343pub fn write_receive_pack_push_request(
5344 writer: &mut impl Write,
5345 request: &ReceivePackPushRequest,
5346) -> Result<()> {
5347 write_receive_pack_request(writer, &request.commands)?;
5348 if let Some(push_options) = &request.push_options {
5349 write_receive_pack_push_options(writer, push_options)?;
5350 }
5351 writer.write_all(&request.packfile)?;
5352 Ok(())
5353}
5354
5355pub fn parse_receive_pack_features(capabilities: &[Capability]) -> Result<ReceivePackFeatures> {
5356 let mut features = ReceivePackFeatures::default();
5357 for capability in capabilities {
5358 match capability.name.as_str() {
5359 "report-status" => {
5360 reject_capability_value("receive-pack report-status", capability)?;
5361 if features.report_status {
5362 return Err(GitError::InvalidFormat(
5363 "receive-pack has duplicate report-status capability".into(),
5364 ));
5365 }
5366 features.report_status = true;
5367 }
5368 "report-status-v2" => {
5369 reject_capability_value("receive-pack report-status-v2", capability)?;
5370 if features.report_status_v2 {
5371 return Err(GitError::InvalidFormat(
5372 "receive-pack has duplicate report-status-v2 capability".into(),
5373 ));
5374 }
5375 features.report_status_v2 = true;
5376 }
5377 "delete-refs" => {
5378 reject_capability_value("receive-pack delete-refs", capability)?;
5379 if features.delete_refs {
5380 return Err(GitError::InvalidFormat(
5381 "receive-pack has duplicate delete-refs capability".into(),
5382 ));
5383 }
5384 features.delete_refs = true;
5385 }
5386 "ofs-delta" => {
5387 reject_capability_value("receive-pack ofs-delta", capability)?;
5388 if features.ofs_delta {
5389 return Err(GitError::InvalidFormat(
5390 "receive-pack has duplicate ofs-delta capability".into(),
5391 ));
5392 }
5393 features.ofs_delta = true;
5394 }
5395 "atomic" => {
5396 reject_capability_value("receive-pack atomic", capability)?;
5397 if features.atomic {
5398 return Err(GitError::InvalidFormat(
5399 "receive-pack has duplicate atomic capability".into(),
5400 ));
5401 }
5402 features.atomic = true;
5403 }
5404 "push-options" => {
5405 reject_capability_value("receive-pack push-options", capability)?;
5406 if features.push_options {
5407 return Err(GitError::InvalidFormat(
5408 "receive-pack has duplicate push-options capability".into(),
5409 ));
5410 }
5411 features.push_options = true;
5412 }
5413 "side-band-64k" => {
5414 reject_capability_value("receive-pack side-band-64k", capability)?;
5415 if features.side_band_64k {
5416 return Err(GitError::InvalidFormat(
5417 "receive-pack has duplicate side-band-64k capability".into(),
5418 ));
5419 }
5420 features.side_band_64k = true;
5421 }
5422 "quiet" => {
5423 reject_capability_value("receive-pack quiet", capability)?;
5424 if features.quiet {
5425 return Err(GitError::InvalidFormat(
5426 "receive-pack has duplicate quiet capability".into(),
5427 ));
5428 }
5429 features.quiet = true;
5430 }
5431 "no-thin" => {
5432 reject_capability_value("receive-pack no-thin", capability)?;
5433 if features.no_thin {
5434 return Err(GitError::InvalidFormat(
5435 "receive-pack has duplicate no-thin capability".into(),
5436 ));
5437 }
5438 features.no_thin = true;
5439 }
5440 "agent" => {
5441 let Some(agent) = &capability.value else {
5442 return Err(GitError::InvalidFormat(
5443 "receive-pack agent capability is missing value".into(),
5444 ));
5445 };
5446 if features.agent.is_some() {
5447 return Err(GitError::InvalidFormat(
5448 "receive-pack has duplicate agent capability".into(),
5449 ));
5450 }
5451 validate_capability_field("receive-pack agent", agent)?;
5452 features.agent = Some(agent.clone());
5453 }
5454 "object-format" => {
5455 let Some(format) = &capability.value else {
5456 return Err(GitError::InvalidFormat(
5457 "receive-pack object-format capability is missing value".into(),
5458 ));
5459 };
5460 if features.object_format.is_some() {
5461 return Err(GitError::InvalidFormat(
5462 "receive-pack has duplicate object-format capability".into(),
5463 ));
5464 }
5465 validate_capability_field("receive-pack object-format", format)?;
5466 features.object_format = Some(format.parse()?);
5467 }
5468 _ => {
5469 encode_capabilities(std::slice::from_ref(capability))?;
5470 if features
5471 .unknown
5472 .iter()
5473 .any(|known| known.name == capability.name)
5474 {
5475 return Err(GitError::InvalidFormat(format!(
5476 "receive-pack has duplicate {} capability",
5477 capability.name
5478 )));
5479 }
5480 features.unknown.push(capability.clone());
5481 }
5482 }
5483 }
5484 Ok(features)
5485}
5486
5487pub fn encode_receive_pack_features(features: &ReceivePackFeatures) -> Result<Vec<Capability>> {
5488 let mut capabilities = Vec::new();
5489 if features.report_status {
5490 capabilities.push(Capability {
5491 name: "report-status".into(),
5492 value: None,
5493 });
5494 }
5495 if features.report_status_v2 {
5496 capabilities.push(Capability {
5497 name: "report-status-v2".into(),
5498 value: None,
5499 });
5500 }
5501 if features.delete_refs {
5502 capabilities.push(Capability {
5503 name: "delete-refs".into(),
5504 value: None,
5505 });
5506 }
5507 if features.ofs_delta {
5508 capabilities.push(Capability {
5509 name: "ofs-delta".into(),
5510 value: None,
5511 });
5512 }
5513 if features.atomic {
5514 capabilities.push(Capability {
5515 name: "atomic".into(),
5516 value: None,
5517 });
5518 }
5519 if features.push_options {
5520 capabilities.push(Capability {
5521 name: "push-options".into(),
5522 value: None,
5523 });
5524 }
5525 if features.side_band_64k {
5526 capabilities.push(Capability {
5527 name: "side-band-64k".into(),
5528 value: None,
5529 });
5530 }
5531 if features.quiet {
5532 capabilities.push(Capability {
5533 name: "quiet".into(),
5534 value: None,
5535 });
5536 }
5537 if features.no_thin {
5538 capabilities.push(Capability {
5539 name: "no-thin".into(),
5540 value: None,
5541 });
5542 }
5543 if let Some(agent) = &features.agent {
5544 validate_capability_field("receive-pack agent", agent)?;
5545 capabilities.push(Capability {
5546 name: "agent".into(),
5547 value: Some(agent.clone()),
5548 });
5549 }
5550 if let Some(format) = features.object_format {
5551 capabilities.push(Capability {
5552 name: "object-format".into(),
5553 value: Some(format.name().into()),
5554 });
5555 }
5556 for capability in &features.unknown {
5557 if is_known_receive_pack_capability(&capability.name) {
5558 return Err(GitError::InvalidFormat(format!(
5559 "receive-pack unknown capability duplicates known capability {}",
5560 capability.name
5561 )));
5562 }
5563 encode_capabilities(std::slice::from_ref(capability))?;
5564 capabilities.push(capability.clone());
5565 }
5566 Ok(capabilities)
5567}
5568
5569pub fn validate_receive_pack_push_request_features(
5570 features: &ReceivePackFeatures,
5571 request: &ReceivePackPushRequest,
5572) -> Result<()> {
5573 for capability in &request.commands.capabilities {
5574 if matches!(
5575 capability.name.as_str(),
5576 "report-status"
5577 | "report-status-v2"
5578 | "delete-refs"
5579 | "ofs-delta"
5580 | "atomic"
5581 | "push-options"
5582 | "side-band-64k"
5583 | "quiet"
5584 | "no-thin"
5585 ) {
5586 reject_capability_value("receive-pack request capability", capability)?;
5587 }
5588 match capability.name.as_str() {
5589 "report-status" if !features.report_status => {
5590 return Err(GitError::InvalidFormat(
5591 "receive-pack request uses report-status without advertised capability".into(),
5592 ));
5593 }
5594 "report-status-v2" if !features.report_status_v2 => {
5595 return Err(GitError::InvalidFormat(
5596 "receive-pack request uses report-status-v2 without advertised capability"
5597 .into(),
5598 ));
5599 }
5600 "delete-refs" if !features.delete_refs => {
5601 return Err(GitError::InvalidFormat(
5602 "receive-pack request uses delete-refs without advertised capability".into(),
5603 ));
5604 }
5605 "ofs-delta" if !features.ofs_delta => {
5606 return Err(GitError::InvalidFormat(
5607 "receive-pack request uses ofs-delta without advertised capability".into(),
5608 ));
5609 }
5610 "atomic" if !features.atomic => {
5611 return Err(GitError::InvalidFormat(
5612 "receive-pack request uses atomic without advertised capability".into(),
5613 ));
5614 }
5615 "push-options" if !features.push_options => {
5616 return Err(GitError::InvalidFormat(
5617 "receive-pack request uses push-options without advertised capability".into(),
5618 ));
5619 }
5620 "side-band-64k" if !features.side_band_64k => {
5621 return Err(GitError::InvalidFormat(
5622 "receive-pack request uses side-band-64k without advertised capability".into(),
5623 ));
5624 }
5625 "quiet" if !features.quiet => {
5626 return Err(GitError::InvalidFormat(
5627 "receive-pack request uses quiet without advertised capability".into(),
5628 ));
5629 }
5630 "no-thin" => {
5631 return Err(GitError::InvalidFormat(
5632 "receive-pack request must not request no-thin".into(),
5633 ));
5634 }
5635 "agent" => {
5636 validate_capability_field(
5637 "receive-pack request agent",
5638 capability.value.as_deref().unwrap_or_default(),
5639 )?;
5640 }
5641 "object-format" => {
5642 let Some(value) = &capability.value else {
5643 return Err(GitError::InvalidFormat(
5644 "receive-pack request object-format capability is missing value".into(),
5645 ));
5646 };
5647 let requested_format: ObjectFormat = value.parse()?;
5648 if features.object_format != Some(requested_format) {
5649 return Err(GitError::InvalidFormat(
5650 "receive-pack request object-format was not advertised".into(),
5651 ));
5652 }
5653 }
5654 name if is_known_receive_pack_capability(name) => {}
5655 _ => {
5656 if !features
5657 .unknown
5658 .iter()
5659 .any(|advertised| advertised.name == capability.name)
5660 {
5661 return Err(GitError::InvalidFormat(format!(
5662 "receive-pack request uses unadvertised capability {}",
5663 capability.name
5664 )));
5665 }
5666 }
5667 }
5668 }
5669
5670 let requested_push_options = request
5671 .commands
5672 .capabilities
5673 .iter()
5674 .any(|capability| capability.name == "push-options");
5675 match (requested_push_options, &request.push_options) {
5676 (true, Some(_)) => {}
5677 (true, None) => {
5678 return Err(GitError::InvalidFormat(
5679 "receive-pack request uses push-options without push-options section".into(),
5680 ));
5681 }
5682 (false, Some(_)) => {
5683 return Err(GitError::InvalidFormat(
5684 "receive-pack request has push-options section without requested capability".into(),
5685 ));
5686 }
5687 (false, None) => {}
5688 }
5689
5690 let has_delete = request
5691 .commands
5692 .commands
5693 .iter()
5694 .any(is_receive_pack_delete_command);
5695 if has_delete && !features.delete_refs {
5696 return Err(GitError::InvalidFormat(
5697 "receive-pack request deletes refs without advertised delete-refs capability".into(),
5698 ));
5699 }
5700
5701 let has_update_or_create = request
5702 .commands
5703 .commands
5704 .iter()
5705 .any(|command| !is_receive_pack_delete_command(command));
5706 if !has_update_or_create && !request.packfile.is_empty() {
5707 return Err(GitError::InvalidFormat(
5708 "receive-pack delete-only request must not include packfile".into(),
5709 ));
5710 }
5711 Ok(())
5712}
5713
5714pub fn apply_receive_pack_push_request<R, I, C, U, D>(
5715 features: &ReceivePackFeatures,
5716 request: &ReceivePackPushRequest,
5717 mut read_ref: R,
5718 mut install_pack: I,
5719 mut contains_object: C,
5720 mut apply_updates: U,
5721 mut delete_ref: D,
5722) -> Result<ReceivePackReportStatus>
5723where
5724 R: FnMut(&str) -> Result<Option<ObjectId>>,
5725 I: FnMut(&[u8]) -> Result<()>,
5726 C: FnMut(&ObjectId) -> Result<bool>,
5727 U: FnMut(&[ReceivePackCommand]) -> Result<()>,
5728 D: FnMut(&ReceivePackCommand) -> Result<()>,
5729{
5730 validate_receive_pack_push_request_features(features, request)?;
5731
5732 for command in request
5733 .commands
5734 .commands
5735 .iter()
5736 .filter(|command| is_receive_pack_delete_command(command))
5737 {
5738 if !command.old_id.is_null() && read_ref(&command.name)? != Some(command.old_id.clone()) {
5739 return Err(GitError::Transaction(format!(
5740 "expected ref {} to match",
5741 command.name
5742 )));
5743 }
5744 }
5745
5746 let updates = request
5747 .commands
5748 .commands
5749 .iter()
5750 .filter(|command| !is_receive_pack_delete_command(command))
5751 .cloned()
5752 .collect::<Vec<_>>();
5753 if !updates.is_empty() {
5754 if !request.packfile.is_empty() {
5755 install_pack(&request.packfile)?;
5756 }
5757 for command in &updates {
5758 if !contains_object(&command.new_id)? {
5759 return Err(GitError::InvalidObject(format!(
5760 "receive-pack packfile did not provide {}",
5761 command.new_id
5762 )));
5763 }
5764 }
5765 apply_updates(&updates)?;
5766 }
5767
5768 for command in request
5769 .commands
5770 .commands
5771 .iter()
5772 .filter(|command| is_receive_pack_delete_command(command))
5773 {
5774 delete_ref(command)?;
5775 }
5776
5777 Ok(ReceivePackReportStatus {
5778 unpack: ReceivePackUnpackStatus::Ok,
5779 commands: request
5780 .commands
5781 .commands
5782 .iter()
5783 .map(|command| ReceivePackCommandStatus::Ok {
5784 name: command.name.clone(),
5785 })
5786 .collect(),
5787 })
5788}
5789
5790pub fn parse_receive_pack_report_status(
5791 frames: &[PktLineFrame],
5792) -> Result<ReceivePackReportStatus> {
5793 let Some((first, rest)) = frames.split_first() else {
5794 return Err(GitError::InvalidFormat(
5795 "receive-pack report-status is empty".into(),
5796 ));
5797 };
5798 let PktLineFrame::Data(payload) = first else {
5799 return Err(GitError::InvalidFormat(
5800 "receive-pack report-status must start with unpack status".into(),
5801 ));
5802 };
5803 let unpack = parse_receive_pack_unpack_status(payload)?;
5804
5805 let mut commands = Vec::new();
5806 let mut saw_flush = false;
5807 for (idx, frame) in rest.iter().enumerate() {
5808 match frame {
5809 PktLineFrame::Data(payload) if !saw_flush => {
5810 commands.push(parse_receive_pack_command_status(payload)?);
5811 }
5812 PktLineFrame::Data(_) => {
5813 return Err(GitError::InvalidFormat(
5814 "receive-pack report-status has data after flush".into(),
5815 ));
5816 }
5817 PktLineFrame::Flush => {
5818 saw_flush = true;
5819 if idx + 1 != rest.len() {
5820 return Err(GitError::InvalidFormat(
5821 "receive-pack report-status has frames after flush".into(),
5822 ));
5823 }
5824 }
5825 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5826 return Err(GitError::InvalidFormat(
5827 "receive-pack report-status contains a non-flush control packet".into(),
5828 ));
5829 }
5830 }
5831 }
5832 if !saw_flush {
5833 return Err(GitError::InvalidFormat(
5834 "receive-pack report-status missing flush".into(),
5835 ));
5836 }
5837 Ok(ReceivePackReportStatus { unpack, commands })
5838}
5839
5840pub fn encode_receive_pack_report_status(
5841 report: &ReceivePackReportStatus,
5842) -> Result<Vec<PktLineFrame>> {
5843 let mut frames = Vec::new();
5844 frames.push(PktLineFrame::data(line_from_str(
5845 &format_receive_pack_unpack_status(&report.unpack)?,
5846 ))?);
5847 for command in &report.commands {
5848 frames.push(PktLineFrame::data(line_from_str(
5849 &format_receive_pack_command_status(command)?,
5850 ))?);
5851 }
5852 frames.push(PktLineFrame::Flush);
5853 Ok(frames)
5854}
5855
5856pub fn read_receive_pack_report_status(reader: &mut impl Read) -> Result<ReceivePackReportStatus> {
5857 let frames = read_pkt_line_frames_until_flush(reader)?;
5858 parse_receive_pack_report_status(&frames)
5859}
5860
5861pub fn write_receive_pack_report_status(
5862 writer: &mut impl Write,
5863 report: &ReceivePackReportStatus,
5864) -> Result<()> {
5865 write_pkt_line_payload(
5866 writer,
5867 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5868 )?;
5869 for command in &report.commands {
5870 write_pkt_line_payload(
5871 writer,
5872 &line_from_str(&format_receive_pack_command_status(command)?),
5873 )?;
5874 }
5875 writer.write_all(b"0000")?;
5876 Ok(())
5877}
5878
5879pub fn parse_receive_pack_report_status_v2(
5880 format: ObjectFormat,
5881 frames: &[PktLineFrame],
5882) -> Result<ReceivePackReportStatusV2> {
5883 let Some((first, rest)) = frames.split_first() else {
5884 return Err(GitError::InvalidFormat(
5885 "receive-pack report-status-v2 is empty".into(),
5886 ));
5887 };
5888 let PktLineFrame::Data(payload) = first else {
5889 return Err(GitError::InvalidFormat(
5890 "receive-pack report-status-v2 must start with unpack status".into(),
5891 ));
5892 };
5893 let unpack = parse_receive_pack_unpack_status(payload)?;
5894
5895 let mut commands = Vec::new();
5896 let mut saw_flush = false;
5897 for (idx, frame) in rest.iter().enumerate() {
5898 match frame {
5899 PktLineFrame::Data(payload) if !saw_flush => {
5900 let text =
5901 parse_protocol_v2_line_text("receive-pack report-status-v2 line", payload)?;
5902 if text.starts_with("option ") {
5903 let Some(ReceivePackCommandStatusV2::Ok { options, .. }) = commands.last_mut()
5904 else {
5905 return Err(GitError::InvalidFormat(
5906 "receive-pack report-status-v2 option without ok status".into(),
5907 ));
5908 };
5909 parse_receive_pack_report_status_v2_option(format, text, options)?;
5910 } else {
5911 commands.push(parse_receive_pack_command_status_v2(text)?);
5912 }
5913 }
5914 PktLineFrame::Data(_) => {
5915 return Err(GitError::InvalidFormat(
5916 "receive-pack report-status-v2 has data after flush".into(),
5917 ));
5918 }
5919 PktLineFrame::Flush => {
5920 saw_flush = true;
5921 if idx + 1 != rest.len() {
5922 return Err(GitError::InvalidFormat(
5923 "receive-pack report-status-v2 has frames after flush".into(),
5924 ));
5925 }
5926 }
5927 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5928 return Err(GitError::InvalidFormat(
5929 "receive-pack report-status-v2 contains a non-flush control packet".into(),
5930 ));
5931 }
5932 }
5933 }
5934 if !saw_flush {
5935 return Err(GitError::InvalidFormat(
5936 "receive-pack report-status-v2 missing flush".into(),
5937 ));
5938 }
5939 Ok(ReceivePackReportStatusV2 { unpack, commands })
5940}
5941
5942pub fn encode_receive_pack_report_status_v2(
5943 report: &ReceivePackReportStatusV2,
5944) -> Result<Vec<PktLineFrame>> {
5945 let mut frames = Vec::new();
5946 frames.push(PktLineFrame::data(line_from_str(
5947 &format_receive_pack_unpack_status(&report.unpack)?,
5948 ))?);
5949 for command in &report.commands {
5950 frames.push(PktLineFrame::data(line_from_str(
5951 &format_receive_pack_command_status_v2(command)?,
5952 ))?);
5953 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5954 for option in format_receive_pack_report_status_v2_options(options)? {
5955 frames.push(PktLineFrame::data(line_from_str(&option))?);
5956 }
5957 }
5958 }
5959 frames.push(PktLineFrame::Flush);
5960 Ok(frames)
5961}
5962
5963pub fn read_receive_pack_report_status_v2(
5964 format: ObjectFormat,
5965 reader: &mut impl Read,
5966) -> Result<ReceivePackReportStatusV2> {
5967 let frames = read_pkt_line_frames_until_flush(reader)?;
5968 parse_receive_pack_report_status_v2(format, &frames)
5969}
5970
5971pub fn write_receive_pack_report_status_v2(
5972 writer: &mut impl Write,
5973 report: &ReceivePackReportStatusV2,
5974) -> Result<()> {
5975 write_pkt_line_payload(
5976 writer,
5977 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5978 )?;
5979 for command in &report.commands {
5980 write_pkt_line_payload(
5981 writer,
5982 &line_from_str(&format_receive_pack_command_status_v2(command)?),
5983 )?;
5984 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5985 for option in format_receive_pack_report_status_v2_options(options)? {
5986 write_pkt_line_payload(writer, &line_from_str(&option))?;
5987 }
5988 }
5989 }
5990 writer.write_all(b"0000")?;
5991 Ok(())
5992}
5993
5994pub fn parse_receive_pack_push_options(frames: &[PktLineFrame]) -> Result<Vec<String>> {
5995 let mut options = Vec::new();
5996 let mut saw_flush = false;
5997 for (idx, frame) in frames.iter().enumerate() {
5998 match frame {
5999 PktLineFrame::Data(payload) if !saw_flush => {
6000 let option = trim_trailing_lf(payload);
6001 validate_receive_pack_push_option(option)?;
6002 options.push(
6003 std::str::from_utf8(option)
6004 .map_err(|err| GitError::InvalidFormat(err.to_string()))?
6005 .to_string(),
6006 );
6007 }
6008 PktLineFrame::Data(_) => {
6009 return Err(GitError::InvalidFormat(
6010 "receive-pack push-options has data after flush".into(),
6011 ));
6012 }
6013 PktLineFrame::Flush => {
6014 saw_flush = true;
6015 if idx + 1 != frames.len() {
6016 return Err(GitError::InvalidFormat(
6017 "receive-pack push-options has frames after flush".into(),
6018 ));
6019 }
6020 }
6021 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
6022 return Err(GitError::InvalidFormat(
6023 "receive-pack push-options contains a non-flush control packet".into(),
6024 ));
6025 }
6026 }
6027 }
6028 if !saw_flush {
6029 return Err(GitError::InvalidFormat(
6030 "receive-pack push-options missing flush".into(),
6031 ));
6032 }
6033 Ok(options)
6034}
6035
6036pub fn encode_receive_pack_push_options(options: &[String]) -> Result<Vec<PktLineFrame>> {
6037 let mut frames = Vec::new();
6038 for option in options {
6039 validate_receive_pack_push_option(option.as_bytes())?;
6040 let mut payload = option.as_bytes().to_vec();
6041 payload.push(b'\n');
6042 frames.push(PktLineFrame::data(payload)?);
6043 }
6044 frames.push(PktLineFrame::Flush);
6045 Ok(frames)
6046}
6047
6048pub fn read_receive_pack_push_options(reader: &mut impl Read) -> Result<Vec<String>> {
6049 let frames = read_pkt_line_frames_until_flush(reader)?;
6050 parse_receive_pack_push_options(&frames)
6051}
6052
6053pub fn write_receive_pack_push_options(writer: &mut impl Write, options: &[String]) -> Result<()> {
6054 for option in options {
6055 validate_receive_pack_push_option(option.as_bytes())?;
6056 let mut payload = option.as_bytes().to_vec();
6057 payload.push(b'\n');
6058 write_pkt_line_payload(writer, &payload)?;
6059 }
6060 writer.write_all(b"0000")?;
6061 Ok(())
6062}
6063
6064fn parse_receive_pack_command(format: ObjectFormat, payload: &[u8]) -> Result<ReceivePackCommand> {
6065 let text =
6066 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6067 let mut fields = text.split(' ');
6068 let old_id = fields
6069 .next()
6070 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing old id".into()))?;
6071 let new_id = fields
6072 .next()
6073 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing new id".into()))?;
6074 let name = fields
6075 .next()
6076 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing ref name".into()))?;
6077 if fields.next().is_some() {
6078 return Err(GitError::InvalidFormat(
6079 "receive-pack command has too many fields".into(),
6080 ));
6081 }
6082 validate_protocol_v2_token("receive-pack old id", old_id)?;
6083 validate_protocol_v2_token("receive-pack new id", new_id)?;
6084 validate_protocol_v2_token("receive-pack ref name", name)?;
6085 Ok(ReceivePackCommand {
6086 old_id: ObjectId::from_hex(format, old_id)?,
6087 new_id: ObjectId::from_hex(format, new_id)?,
6088 name: name.to_string(),
6089 })
6090}
6091
6092fn format_receive_pack_command(command: &ReceivePackCommand) -> Result<Vec<u8>> {
6093 if command.old_id.format() != command.new_id.format() {
6094 return Err(GitError::InvalidObjectId(
6095 "receive-pack command object formats do not match".into(),
6096 ));
6097 }
6098 validate_protocol_v2_token("receive-pack ref name", &command.name)?;
6099 Ok(format!("{} {} {}", command.old_id, command.new_id, command.name).into_bytes())
6100}
6101
6102fn set_upload_pack_flag(value: &mut bool, capability: &Capability) -> Result<()> {
6103 reject_capability_value("upload-pack capability", capability)?;
6104 if *value {
6105 return Err(GitError::InvalidFormat(format!(
6106 "upload-pack has duplicate {} capability",
6107 capability.name
6108 )));
6109 }
6110 *value = true;
6111 Ok(())
6112}
6113
6114fn push_upload_pack_flag(capabilities: &mut Vec<Capability>, name: &str, enabled: bool) {
6115 if enabled {
6116 capabilities.push(Capability {
6117 name: name.into(),
6118 value: None,
6119 });
6120 }
6121}
6122
6123fn is_known_upload_pack_capability(name: &str) -> bool {
6124 matches!(
6125 name,
6126 "multi_ack"
6127 | "multi_ack_detailed"
6128 | "no-done"
6129 | "thin-pack"
6130 | "side-band"
6131 | "side-band-64k"
6132 | "ofs-delta"
6133 | "shallow"
6134 | "deepen-since"
6135 | "deepen-not"
6136 | "include-tag"
6137 | "no-progress"
6138 | "allow-tip-sha1-in-want"
6139 | "allow-reachable-sha1-in-want"
6140 | "filter"
6141 | "agent"
6142 | "object-format"
6143 | "symref"
6144 )
6145}
6146
6147fn is_upload_pack_flag_capability(name: &str) -> bool {
6148 matches!(
6149 name,
6150 "multi_ack"
6151 | "multi_ack_detailed"
6152 | "no-done"
6153 | "thin-pack"
6154 | "side-band"
6155 | "side-band-64k"
6156 | "ofs-delta"
6157 | "shallow"
6158 | "deepen-since"
6159 | "deepen-not"
6160 | "include-tag"
6161 | "no-progress"
6162 | "allow-tip-sha1-in-want"
6163 | "allow-reachable-sha1-in-want"
6164 | "filter"
6165 )
6166}
6167
6168fn reject_capability_value(label: &str, capability: &Capability) -> Result<()> {
6169 if capability.value.is_some() {
6170 return Err(GitError::InvalidFormat(format!(
6171 "{label} must not have value"
6172 )));
6173 }
6174 Ok(())
6175}
6176
6177fn is_known_receive_pack_capability(name: &str) -> bool {
6178 matches!(
6179 name,
6180 "report-status"
6181 | "report-status-v2"
6182 | "delete-refs"
6183 | "ofs-delta"
6184 | "atomic"
6185 | "push-options"
6186 | "side-band-64k"
6187 | "quiet"
6188 | "no-thin"
6189 | "agent"
6190 | "object-format"
6191 )
6192}
6193
6194fn is_receive_pack_delete_command(command: &ReceivePackCommand) -> bool {
6195 command.new_id.is_null()
6196}
6197
6198fn parse_receive_pack_unpack_status(payload: &[u8]) -> Result<ReceivePackUnpackStatus> {
6199 let text = parse_protocol_v2_line_text("receive-pack unpack status", payload)?;
6200 if text == "unpack ok" {
6201 return Ok(ReceivePackUnpackStatus::Ok);
6202 }
6203 let Some(message) = text.strip_prefix("unpack ") else {
6204 return Err(GitError::InvalidFormat(format!(
6205 "unsupported receive-pack unpack status {text}"
6206 )));
6207 };
6208 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6209 Ok(ReceivePackUnpackStatus::Error(message.to_string()))
6210}
6211
6212fn format_receive_pack_unpack_status(status: &ReceivePackUnpackStatus) -> Result<String> {
6213 match status {
6214 ReceivePackUnpackStatus::Ok => Ok("unpack ok".into()),
6215 ReceivePackUnpackStatus::Error(message) => {
6216 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6217 Ok(format!("unpack {message}"))
6218 }
6219 }
6220}
6221
6222fn parse_receive_pack_command_status(payload: &[u8]) -> Result<ReceivePackCommandStatus> {
6223 let text = parse_protocol_v2_line_text("receive-pack command status", payload)?;
6224 if let Some(name) = text.strip_prefix("ok ") {
6225 validate_protocol_v2_token("receive-pack status ref name", name)?;
6226 return Ok(ReceivePackCommandStatus::Ok {
6227 name: name.to_string(),
6228 });
6229 }
6230 let Some(rest) = text.strip_prefix("ng ") else {
6231 return Err(GitError::InvalidFormat(format!(
6232 "unsupported receive-pack command status {text}"
6233 )));
6234 };
6235 let (name, message) = rest
6236 .split_once(' ')
6237 .ok_or_else(|| GitError::InvalidFormat("receive-pack ng status missing message".into()))?;
6238 validate_protocol_v2_token("receive-pack status ref name", name)?;
6239 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6240 Ok(ReceivePackCommandStatus::Ng {
6241 name: name.to_string(),
6242 message: message.to_string(),
6243 })
6244}
6245
6246fn format_receive_pack_command_status(status: &ReceivePackCommandStatus) -> Result<String> {
6247 match status {
6248 ReceivePackCommandStatus::Ok { name } => {
6249 validate_protocol_v2_token("receive-pack status ref name", name)?;
6250 Ok(format!("ok {name}"))
6251 }
6252 ReceivePackCommandStatus::Ng { name, message } => {
6253 validate_protocol_v2_token("receive-pack status ref name", name)?;
6254 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6255 Ok(format!("ng {name} {message}"))
6256 }
6257 }
6258}
6259
6260fn parse_receive_pack_command_status_v2(text: &str) -> Result<ReceivePackCommandStatusV2> {
6261 if let Some(name) = text.strip_prefix("ok ") {
6262 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6263 return Ok(ReceivePackCommandStatusV2::Ok {
6264 name: name.to_string(),
6265 options: ReceivePackCommandStatusV2Options::default(),
6266 });
6267 }
6268 let Some(rest) = text.strip_prefix("ng ") else {
6269 return Err(GitError::InvalidFormat(format!(
6270 "unsupported receive-pack report-status-v2 line {text}"
6271 )));
6272 };
6273 let (name, message) = rest.split_once(' ').ok_or_else(|| {
6274 GitError::InvalidFormat("receive-pack status-v2 ng status missing message".into())
6275 })?;
6276 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6277 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6278 Ok(ReceivePackCommandStatusV2::Ng {
6279 name: name.to_string(),
6280 message: message.to_string(),
6281 })
6282}
6283
6284fn format_receive_pack_command_status_v2(status: &ReceivePackCommandStatusV2) -> Result<String> {
6285 match status {
6286 ReceivePackCommandStatusV2::Ok { name, .. } => {
6287 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6288 Ok(format!("ok {name}"))
6289 }
6290 ReceivePackCommandStatusV2::Ng { name, message } => {
6291 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6292 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6293 Ok(format!("ng {name} {message}"))
6294 }
6295 }
6296}
6297
6298fn parse_receive_pack_report_status_v2_option(
6299 format: ObjectFormat,
6300 text: &str,
6301 options: &mut ReceivePackCommandStatusV2Options,
6302) -> Result<()> {
6303 if let Some(refname) = text.strip_prefix("option refname ") {
6304 if options.refname.is_some() {
6305 return Err(GitError::InvalidFormat(
6306 "receive-pack report-status-v2 has duplicate refname option".into(),
6307 ));
6308 }
6309 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6310 options.refname = Some(refname.to_string());
6311 } else if let Some(old_oid) = text.strip_prefix("option old-oid ") {
6312 if options.old_oid.is_some() {
6313 return Err(GitError::InvalidFormat(
6314 "receive-pack report-status-v2 has duplicate old-oid option".into(),
6315 ));
6316 }
6317 validate_protocol_v2_token("receive-pack status-v2 option old-oid", old_oid)?;
6318 options.old_oid = Some(ObjectId::from_hex(format, old_oid)?);
6319 } else if let Some(new_oid) = text.strip_prefix("option new-oid ") {
6320 if options.new_oid.is_some() {
6321 return Err(GitError::InvalidFormat(
6322 "receive-pack report-status-v2 has duplicate new-oid option".into(),
6323 ));
6324 }
6325 validate_protocol_v2_token("receive-pack status-v2 option new-oid", new_oid)?;
6326 options.new_oid = Some(ObjectId::from_hex(format, new_oid)?);
6327 } else if text == "option forced-update" {
6328 if options.forced_update {
6329 return Err(GitError::InvalidFormat(
6330 "receive-pack report-status-v2 has duplicate forced-update option".into(),
6331 ));
6332 }
6333 options.forced_update = true;
6334 } else {
6335 return Err(GitError::InvalidFormat(format!(
6336 "unsupported receive-pack report-status-v2 option {text}"
6337 )));
6338 }
6339 Ok(())
6340}
6341
6342fn format_receive_pack_report_status_v2_options(
6343 options: &ReceivePackCommandStatusV2Options,
6344) -> Result<Vec<String>> {
6345 let mut out = Vec::new();
6346 if let Some(refname) = &options.refname {
6347 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6348 out.push(format!("option refname {refname}"));
6349 }
6350 if let Some(old_oid) = &options.old_oid {
6351 out.push(format!("option old-oid {old_oid}"));
6352 }
6353 if let Some(new_oid) = &options.new_oid {
6354 out.push(format!("option new-oid {new_oid}"));
6355 }
6356 if options.forced_update {
6357 out.push("option forced-update".into());
6358 }
6359 Ok(out)
6360}
6361
6362fn validate_receive_pack_status_message(label: &str, message: &str) -> Result<()> {
6363 if message.is_empty() {
6364 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6365 }
6366 if message
6367 .bytes()
6368 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6369 {
6370 return Err(GitError::InvalidFormat(format!(
6371 "{label} contains a delimiter byte"
6372 )));
6373 }
6374 Ok(())
6375}
6376
6377fn validate_receive_pack_push_option(option: &[u8]) -> Result<()> {
6378 if option.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6379 return Err(GitError::InvalidFormat(
6380 "receive-pack push-option contains a delimiter byte".into(),
6381 ));
6382 }
6383 Ok(())
6384}
6385
6386fn validate_protocol_error_message(message: &str) -> Result<()> {
6387 if message.is_empty() {
6388 return Err(GitError::InvalidFormat(
6389 "protocol error message is empty".into(),
6390 ));
6391 }
6392 if message
6393 .bytes()
6394 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6395 {
6396 return Err(GitError::InvalidFormat(
6397 "protocol error message contains a delimiter byte".into(),
6398 ));
6399 }
6400 Ok(())
6401}
6402
6403fn parse_capability_token(token: &str) -> Result<Capability> {
6404 if token.is_empty() {
6405 return Err(GitError::InvalidFormat("empty capability token".into()));
6406 }
6407 let (name, value) = token
6408 .split_once('=')
6409 .map_or((token, None), |(name, value)| (name, Some(value)));
6410 validate_capability_field("capability name", name)?;
6411 if let Some(value) = value {
6412 validate_capability_field("capability value", value)?;
6413 }
6414 Ok(Capability {
6415 name: name.to_string(),
6416 value: value.map(str::to_string),
6417 })
6418}
6419
6420fn parse_protocol_v2_capability_line(payload: &[u8]) -> Result<Capability> {
6421 let payload = trim_trailing_lf(payload);
6422 if payload.is_empty() {
6423 return Err(GitError::InvalidFormat(
6424 "empty protocol v2 capability line".into(),
6425 ));
6426 }
6427 let text =
6428 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6429 let (name, value) = text
6430 .split_once('=')
6431 .map_or((text, None), |(name, value)| (name, Some(value)));
6432 validate_capability_name(name)?;
6433 if let Some(value) = value {
6434 validate_protocol_v2_capability_value(value)?;
6435 }
6436 Ok(Capability {
6437 name: name.to_string(),
6438 value: value.map(str::to_string),
6439 })
6440}
6441
6442fn parse_protocol_v2_command_line(payload: &[u8]) -> Result<String> {
6443 let payload = trim_trailing_lf(payload);
6444 let text =
6445 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6446 let Some(command) = text.strip_prefix("command=") else {
6447 return Err(GitError::InvalidFormat(
6448 "protocol v2 command request missing command prefix".into(),
6449 ));
6450 };
6451 validate_capability_name(command)?;
6452 Ok(command.to_string())
6453}
6454
6455fn parse_protocol_v2_ls_refs_line(
6456 format: ObjectFormat,
6457 payload: &[u8],
6458) -> Result<ProtocolV2LsRefsRecord> {
6459 let payload = trim_trailing_lf(payload);
6460 if payload.is_empty() {
6461 return Err(GitError::InvalidFormat(
6462 "ls-refs response line is empty".into(),
6463 ));
6464 }
6465 let text =
6466 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6467 let mut fields = text.split(' ');
6468 let first = fields
6469 .next()
6470 .ok_or_else(|| GitError::InvalidFormat("ls-refs response line is empty".into()))?;
6471 if first == "unborn" {
6472 let name = fields
6473 .next()
6474 .ok_or_else(|| GitError::InvalidFormat("ls-refs unborn line is missing name".into()))?;
6475 validate_protocol_v2_token("ls-refs ref name", name)?;
6476 let (symref_target, attributes) = parse_protocol_v2_ls_refs_attributes(format, fields)?;
6477 return Ok(ProtocolV2LsRefsRecord::Unborn {
6478 name: name.to_string(),
6479 symref_target,
6480 attributes,
6481 });
6482 }
6483
6484 let oid = ObjectId::from_hex(format, first)?;
6485 let name = fields
6486 .next()
6487 .ok_or_else(|| GitError::InvalidFormat("ls-refs ref line is missing name".into()))?;
6488 validate_protocol_v2_token("ls-refs ref name", name)?;
6489 let (peeled, symref_target, attributes) =
6490 parse_protocol_v2_ls_refs_ref_attributes(format, fields)?;
6491 Ok(ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
6492 oid,
6493 name: name.to_string(),
6494 peeled,
6495 symref_target,
6496 attributes,
6497 }))
6498}
6499
6500fn parse_protocol_v2_ls_refs_ref_attributes<'a>(
6501 format: ObjectFormat,
6502 fields: impl Iterator<Item = &'a str>,
6503) -> Result<(Option<ObjectId>, Option<String>, Vec<String>)> {
6504 let mut peeled = None;
6505 let (symref_target, attributes) =
6506 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6507 if let Some(value) = attr.strip_prefix("peeled:") {
6508 if peeled.is_some() {
6509 return Err(GitError::InvalidFormat(
6510 "ls-refs response has duplicate peeled attribute".into(),
6511 ));
6512 }
6513 peeled = Some(ObjectId::from_hex(format, value)?);
6514 return Ok(true);
6515 }
6516 Ok(false)
6517 })?;
6518 Ok((peeled, symref_target, attributes))
6519}
6520
6521fn parse_protocol_v2_ls_refs_attributes<'a>(
6522 format: ObjectFormat,
6523 fields: impl Iterator<Item = &'a str>,
6524) -> Result<(Option<String>, Vec<String>)> {
6525 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6526 if attr.starts_with("peeled:") {
6527 return Err(GitError::InvalidFormat(
6528 "ls-refs unborn line has peeled attribute".into(),
6529 ));
6530 }
6531 Ok(false)
6532 })
6533}
6534
6535fn parse_protocol_v2_ls_refs_attributes_with<'a, F>(
6536 _format: ObjectFormat,
6537 fields: impl Iterator<Item = &'a str>,
6538 mut handle_known: F,
6539) -> Result<(Option<String>, Vec<String>)>
6540where
6541 F: FnMut(&str) -> Result<bool>,
6542{
6543 let mut symref_target = None;
6544 let mut attributes = Vec::new();
6545 for attr in fields {
6546 validate_protocol_v2_token("ls-refs attribute", attr)?;
6547 if let Some(value) = attr.strip_prefix("symref-target:") {
6548 if symref_target.is_some() {
6549 return Err(GitError::InvalidFormat(
6550 "ls-refs response has duplicate symref-target attribute".into(),
6551 ));
6552 }
6553 validate_protocol_v2_token("ls-refs symref-target", value)?;
6554 symref_target = Some(value.to_string());
6555 } else if !handle_known(attr)? {
6556 attributes.push(attr.to_string());
6557 }
6558 }
6559 Ok((symref_target, attributes))
6560}
6561
6562fn format_protocol_v2_ls_refs_record(record: &ProtocolV2LsRefsRecord) -> Result<String> {
6563 let mut out = String::new();
6564 match record {
6565 ProtocolV2LsRefsRecord::Ref(reference) => {
6566 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
6567 out.push_str(&reference.oid.to_string());
6568 out.push(' ');
6569 out.push_str(&reference.name);
6570 if let Some(peeled) = &reference.peeled {
6571 if peeled.format() != reference.oid.format() {
6572 return Err(GitError::InvalidObjectId(
6573 "ls-refs peeled object format does not match ref object format".into(),
6574 ));
6575 }
6576 out.push(' ');
6577 out.push_str("peeled:");
6578 out.push_str(&peeled.to_string());
6579 }
6580 if let Some(target) = &reference.symref_target {
6581 validate_protocol_v2_token("ls-refs symref-target", target)?;
6582 out.push(' ');
6583 out.push_str("symref-target:");
6584 out.push_str(target);
6585 }
6586 append_protocol_v2_ls_refs_attributes(&mut out, &reference.attributes)?;
6587 }
6588 ProtocolV2LsRefsRecord::Unborn {
6589 name,
6590 symref_target,
6591 attributes,
6592 } => {
6593 validate_protocol_v2_token("ls-refs ref name", name)?;
6594 out.push_str("unborn ");
6595 out.push_str(name);
6596 if let Some(target) = symref_target {
6597 validate_protocol_v2_token("ls-refs symref-target", target)?;
6598 out.push(' ');
6599 out.push_str("symref-target:");
6600 out.push_str(target);
6601 }
6602 append_protocol_v2_ls_refs_attributes(&mut out, attributes)?;
6603 }
6604 }
6605 Ok(out)
6606}
6607
6608fn append_protocol_v2_ls_refs_attributes(out: &mut String, attributes: &[String]) -> Result<()> {
6609 for attr in attributes {
6610 validate_protocol_v2_token("ls-refs attribute", attr)?;
6611 if attr.starts_with("peeled:") || attr.starts_with("symref-target:") {
6612 return Err(GitError::InvalidFormat(
6613 "ls-refs generic attributes must not duplicate known attributes".into(),
6614 ));
6615 }
6616 out.push(' ');
6617 out.push_str(attr);
6618 }
6619 Ok(())
6620}
6621
6622fn parse_fetch_section_header(payload: &[u8]) -> Result<String> {
6623 let name = parse_protocol_v2_line_text("fetch response section", payload)?;
6624 validate_capability_name(name)?;
6625 Ok(name.to_string())
6626}
6627
6628fn flush_terminates_protocol_v2_response(frames: &[PktLineFrame], idx: usize) -> bool {
6629 idx + 1 == frames.len()
6630 || (idx + 2 == frames.len() && matches!(frames[idx + 1], PktLineFrame::ResponseEnd))
6631}
6632
6633fn parse_fetch_section(
6634 format: ObjectFormat,
6635 name: String,
6636 lines: Vec<Vec<u8>>,
6637) -> Result<ProtocolV2FetchResponseSection> {
6638 match name.as_str() {
6639 "acknowledgments" => lines
6640 .iter()
6641 .map(|line| parse_fetch_acknowledgment(format, line))
6642 .collect::<Result<Vec<_>>>()
6643 .map(ProtocolV2FetchResponseSection::Acknowledgments),
6644 "shallow-info" => lines
6645 .iter()
6646 .map(|line| parse_fetch_shallow_info(format, line))
6647 .collect::<Result<Vec<_>>>()
6648 .map(ProtocolV2FetchResponseSection::ShallowInfo),
6649 "wanted-refs" => lines
6650 .iter()
6651 .map(|line| parse_fetch_wanted_ref(format, line))
6652 .collect::<Result<Vec<_>>>()
6653 .map(ProtocolV2FetchResponseSection::WantedRefs),
6654 "packfile-uris" => lines
6655 .iter()
6656 .map(|line| parse_fetch_packfile_uri(format, line))
6657 .collect::<Result<Vec<_>>>()
6658 .map(ProtocolV2FetchResponseSection::PackfileUris),
6659 "packfile" => Ok(ProtocolV2FetchResponseSection::Packfile(lines)),
6660 _ => Ok(ProtocolV2FetchResponseSection::Unknown { name, lines }),
6661 }
6662}
6663
6664fn parse_fetch_acknowledgment(
6665 format: ObjectFormat,
6666 line: &[u8],
6667) -> Result<ProtocolV2FetchAcknowledgment> {
6668 let text = parse_protocol_v2_line_text("fetch acknowledgment", line)?;
6669 match text {
6670 "NAK" => Ok(ProtocolV2FetchAcknowledgment::Nak),
6671 "ready" => Ok(ProtocolV2FetchAcknowledgment::Ready),
6672 value if value.starts_with("ACK ") => Ok(ProtocolV2FetchAcknowledgment::Ack(
6673 parse_oid_argument(format, "fetch ACK", value, "ACK ")?,
6674 )),
6675 other => Err(GitError::InvalidFormat(format!(
6676 "unsupported fetch acknowledgment {other}"
6677 ))),
6678 }
6679}
6680
6681fn parse_fetch_shallow_info(
6682 format: ObjectFormat,
6683 line: &[u8],
6684) -> Result<ProtocolV2FetchShallowInfo> {
6685 let text = parse_protocol_v2_line_text("fetch shallow-info", line)?;
6686 if text.starts_with("shallow ") {
6687 return Ok(ProtocolV2FetchShallowInfo::Shallow(parse_oid_argument(
6688 format,
6689 "fetch shallow",
6690 text,
6691 "shallow ",
6692 )?));
6693 }
6694 if text.starts_with("unshallow ") {
6695 return Ok(ProtocolV2FetchShallowInfo::Unshallow(parse_oid_argument(
6696 format,
6697 "fetch unshallow",
6698 text,
6699 "unshallow ",
6700 )?));
6701 }
6702 Err(GitError::InvalidFormat(format!(
6703 "unsupported fetch shallow-info {text}"
6704 )))
6705}
6706
6707fn parse_fetch_wanted_ref(format: ObjectFormat, line: &[u8]) -> Result<ProtocolV2FetchWantedRef> {
6708 let text = parse_protocol_v2_line_text("fetch wanted-ref", line)?;
6709 let (oid, name) = text
6710 .split_once(' ')
6711 .ok_or_else(|| GitError::InvalidFormat("fetch wanted-ref is missing name".into()))?;
6712 validate_protocol_v2_token("fetch wanted-ref name", name)?;
6713 Ok(ProtocolV2FetchWantedRef {
6714 oid: ObjectId::from_hex(format, oid)?,
6715 name: name.to_string(),
6716 })
6717}
6718
6719fn parse_fetch_packfile_uri(
6720 format: ObjectFormat,
6721 line: &[u8],
6722) -> Result<ProtocolV2FetchPackfileUri> {
6723 let text = parse_protocol_v2_line_text("fetch packfile-uri", line)?;
6724 let (pack_hash, uri) = text
6725 .split_once(' ')
6726 .ok_or_else(|| GitError::InvalidFormat("fetch packfile-uri is missing uri".into()))?;
6727 validate_protocol_v2_token("fetch packfile-uri hash", pack_hash)?;
6728 validate_protocol_v2_token("fetch packfile-uri", uri)?;
6729 Ok(ProtocolV2FetchPackfileUri {
6730 pack_hash: ObjectId::from_hex(format, pack_hash)?,
6731 uri: uri.to_string(),
6732 })
6733}
6734
6735fn protocol_v2_fetch_section_name(section: &ProtocolV2FetchResponseSection) -> &str {
6736 match section {
6737 ProtocolV2FetchResponseSection::Acknowledgments(_) => "acknowledgments",
6738 ProtocolV2FetchResponseSection::ShallowInfo(_) => "shallow-info",
6739 ProtocolV2FetchResponseSection::WantedRefs(_) => "wanted-refs",
6740 ProtocolV2FetchResponseSection::PackfileUris(_) => "packfile-uris",
6741 ProtocolV2FetchResponseSection::Packfile(_) => "packfile",
6742 ProtocolV2FetchResponseSection::Unknown { name, .. } => name,
6743 }
6744}
6745
6746fn format_protocol_v2_fetch_section_lines(
6747 section: &ProtocolV2FetchResponseSection,
6748) -> Result<Vec<Vec<u8>>> {
6749 match section {
6750 ProtocolV2FetchResponseSection::Acknowledgments(acks) => acks
6751 .iter()
6752 .map(|ack| match ack {
6753 ProtocolV2FetchAcknowledgment::Nak => Ok(line_from_str("NAK")),
6754 ProtocolV2FetchAcknowledgment::Ack(oid) => Ok(line_from_str(&format!("ACK {oid}"))),
6755 ProtocolV2FetchAcknowledgment::Ready => Ok(line_from_str("ready")),
6756 })
6757 .collect(),
6758 ProtocolV2FetchResponseSection::ShallowInfo(entries) => entries
6759 .iter()
6760 .map(|entry| match entry {
6761 ProtocolV2FetchShallowInfo::Shallow(oid) => {
6762 Ok(line_from_str(&format!("shallow {oid}")))
6763 }
6764 ProtocolV2FetchShallowInfo::Unshallow(oid) => {
6765 Ok(line_from_str(&format!("unshallow {oid}")))
6766 }
6767 })
6768 .collect(),
6769 ProtocolV2FetchResponseSection::WantedRefs(refs) => refs
6770 .iter()
6771 .map(|wanted| {
6772 validate_protocol_v2_token("fetch wanted-ref name", &wanted.name)?;
6773 Ok(line_from_str(&format!("{} {}", wanted.oid, wanted.name)))
6774 })
6775 .collect(),
6776 ProtocolV2FetchResponseSection::PackfileUris(uris) => uris
6777 .iter()
6778 .map(|packfile_uri| {
6779 validate_protocol_v2_token("fetch packfile-uri", &packfile_uri.uri)?;
6780 Ok(line_from_str(&format!(
6781 "{} {}",
6782 packfile_uri.pack_hash, packfile_uri.uri
6783 )))
6784 })
6785 .collect(),
6786 ProtocolV2FetchResponseSection::Packfile(lines) => Ok(lines.clone()),
6787 ProtocolV2FetchResponseSection::Unknown { name, lines } => {
6788 validate_capability_name(name)?;
6789 for line in lines {
6790 validate_protocol_v2_line("fetch unknown section line", line)?;
6791 }
6792 Ok(lines.clone())
6793 }
6794 }
6795}
6796
6797fn parse_protocol_v2_object_info_record(
6798 format: ObjectFormat,
6799 line: &[u8],
6800) -> Result<ProtocolV2ObjectInfoRecord> {
6801 let text = parse_protocol_v2_line_text("object-info record", line)?;
6802 let mut fields = text.split(' ');
6803 let oid = fields
6804 .next()
6805 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing oid".into()))?;
6806 let size = fields
6807 .next()
6808 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing size".into()))?;
6809 if fields.next().is_some() {
6810 return Err(GitError::InvalidFormat(
6811 "object-info record has too many fields".into(),
6812 ));
6813 }
6814 validate_protocol_v2_token("object-info oid", oid)?;
6815 validate_protocol_v2_token("object-info size", size)?;
6816 let size = size
6817 .parse::<u64>()
6818 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6819 Ok(ProtocolV2ObjectInfoRecord {
6820 oid: ObjectId::from_hex(format, oid)?,
6821 size,
6822 })
6823}
6824
6825fn parse_dumb_http_info_ref_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpRefRecord> {
6826 validate_dumb_http_info_ref_line(line)?;
6827 let line = trim_trailing_lf(line);
6828 let tab = line
6829 .iter()
6830 .position(|byte| *byte == b'\t')
6831 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP ref record is missing name".into()))?;
6832 let (oid, name) = (&line[..tab], &line[tab + 1..]);
6833 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6834 validate_protocol_v2_token("dumb HTTP ref oid", oid)?;
6835 let name = std::str::from_utf8(name).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6836 let (name, peeled) = name
6837 .strip_suffix("^{}")
6838 .map_or((name, false), |name| (name, true));
6839 validate_dumb_http_ref_name(name)?;
6840 Ok(DumbHttpRefRecord {
6841 oid: ObjectId::from_hex(format, oid)?,
6842 name: name.to_string(),
6843 peeled,
6844 })
6845}
6846
6847fn parse_dumb_http_alternate(line: &[u8]) -> Result<String> {
6848 validate_dumb_http_alternate_line(line)?;
6849 let line = trim_trailing_lf(line);
6850 let alternate =
6851 std::str::from_utf8(line).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6852 validate_dumb_http_alternate(alternate)?;
6853 Ok(alternate.to_string())
6854}
6855
6856fn parse_dumb_http_pack_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpPackRecord> {
6857 validate_dumb_http_info_ref_line(line)?;
6858 let line = parse_protocol_v2_line_text("dumb HTTP pack record", line)?;
6859 let pack_name = line
6860 .strip_prefix("P ")
6861 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP pack record must start with P".into()))?;
6862 let hash = pack_name
6863 .strip_prefix("pack-")
6864 .and_then(|value| value.strip_suffix(".pack"))
6865 .ok_or_else(|| GitError::InvalidFormat("invalid dumb HTTP pack name".into()))?;
6866 validate_protocol_v2_token("dumb HTTP pack hash", hash)?;
6867 Ok(DumbHttpPackRecord {
6868 hash: ObjectId::from_hex(format, hash)?,
6869 })
6870}
6871
6872fn encode_protocol_v2_capability(capability: &Capability) -> Result<Vec<u8>> {
6873 validate_capability_name(&capability.name)?;
6874 let mut out = capability.name.as_bytes().to_vec();
6875 if let Some(value) = &capability.value {
6876 validate_protocol_v2_capability_value(value)?;
6877 out.push(b'=');
6878 out.extend_from_slice(value.as_bytes());
6879 }
6880 Ok(out)
6881}
6882
6883fn validate_capability_field(label: &str, value: &str) -> Result<()> {
6884 if value.is_empty() {
6885 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6886 }
6887 if value
6888 .bytes()
6889 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t' | 0))
6890 {
6891 return Err(GitError::InvalidFormat(format!(
6892 "{label} contains a delimiter byte"
6893 )));
6894 }
6895 Ok(())
6896}
6897
6898fn validate_capability_name(value: &str) -> Result<()> {
6899 validate_capability_field("capability name", value)?;
6900 if value.bytes().any(|byte| byte == b'=') {
6901 return Err(GitError::InvalidFormat(
6902 "capability name contains a delimiter byte".into(),
6903 ));
6904 }
6905 Ok(())
6906}
6907
6908fn validate_protocol_v2_capability_value(value: &str) -> Result<()> {
6909 if value.is_empty() {
6910 return Err(GitError::InvalidFormat(
6911 "protocol v2 capability value is empty".into(),
6912 ));
6913 }
6914 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6915 return Err(GitError::InvalidFormat(
6916 "protocol v2 capability value contains a delimiter byte".into(),
6917 ));
6918 }
6919 Ok(())
6920}
6921
6922fn validate_protocol_v2_argument(value: &[u8]) -> Result<()> {
6923 if value.is_empty() {
6924 return Err(GitError::InvalidFormat(
6925 "protocol v2 command argument is empty".into(),
6926 ));
6927 }
6928 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6929 return Err(GitError::InvalidFormat(
6930 "protocol v2 command argument contains a delimiter byte".into(),
6931 ));
6932 }
6933 Ok(())
6934}
6935
6936fn validate_upload_archive_argument(value: &str) -> Result<()> {
6937 if value.is_empty() {
6938 return Err(GitError::InvalidFormat(
6939 "upload-archive argument is empty".into(),
6940 ));
6941 }
6942 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6943 return Err(GitError::InvalidFormat(
6944 "upload-archive argument contains a delimiter byte".into(),
6945 ));
6946 }
6947 Ok(())
6948}
6949
6950fn validate_upload_archive_status_message(value: &str) -> Result<()> {
6951 if value.is_empty() {
6952 return Err(GitError::InvalidFormat(
6953 "upload-archive status message is empty".into(),
6954 ));
6955 }
6956 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6957 return Err(GitError::InvalidFormat(
6958 "upload-archive status message contains a delimiter byte".into(),
6959 ));
6960 }
6961 Ok(())
6962}
6963
6964fn non_empty(value: &str) -> Option<&str> {
6965 (!value.is_empty()).then_some(value)
6966}
6967
6968fn validate_refspec_value(value: &str) -> Result<()> {
6969 if value.is_empty() {
6970 return Err(GitError::InvalidFormat("refspec is empty".into()));
6971 }
6972 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6973 return Err(GitError::InvalidFormat(
6974 "refspec contains a delimiter byte".into(),
6975 ));
6976 }
6977 Ok(())
6978}
6979
6980fn validate_refspec_endpoint(label: &str, value: &str) -> Result<()> {
6981 if value.is_empty() {
6982 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6983 }
6984 if value
6985 .bytes()
6986 .any(|byte| matches!(byte, b':' | b' ' | b'\t' | b'\n' | b'\r' | 0))
6987 {
6988 return Err(GitError::InvalidFormat(format!(
6989 "{label} contains a delimiter byte"
6990 )));
6991 }
6992 Ok(())
6993}
6994
6995fn count_refspec_wildcards(value: &str) -> usize {
6996 value.bytes().filter(|byte| *byte == b'*').count()
6997}
6998
6999fn validate_refspec_shape(refspec: &RefSpec) -> Result<()> {
7000 if refspec.force && refspec.negative {
7001 return Err(GitError::InvalidFormat(
7002 "negative refspec must not be forced".into(),
7003 ));
7004 }
7005 if refspec.negative && refspec.dst.is_some() {
7006 return Err(GitError::InvalidFormat(
7007 "negative refspec must not have a destination".into(),
7008 ));
7009 }
7010 if refspec.negative && refspec.src.is_none() {
7011 return Err(GitError::InvalidFormat(
7012 "negative refspec is missing a source".into(),
7013 ));
7014 }
7015 if refspec.src.is_none() && refspec.dst.is_none() && refspec.negative {
7016 return Err(GitError::InvalidFormat(
7017 "refspec must include a source or destination".into(),
7018 ));
7019 }
7020 if let Some(src) = &refspec.src {
7021 validate_refspec_endpoint("refspec source", src)?;
7022 }
7023 if let Some(dst) = &refspec.dst {
7024 validate_refspec_endpoint("refspec destination", dst)?;
7025 }
7026 let src_pattern_count = refspec
7027 .src
7028 .as_deref()
7029 .map(count_refspec_wildcards)
7030 .unwrap_or(0);
7031 let dst_pattern_count = refspec
7032 .dst
7033 .as_deref()
7034 .map(count_refspec_wildcards)
7035 .unwrap_or(0);
7036 if src_pattern_count > 1 || dst_pattern_count > 1 {
7037 return Err(GitError::InvalidFormat(
7038 "refspec endpoint has too many wildcards".into(),
7039 ));
7040 }
7041 if refspec.dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
7042 return Err(GitError::InvalidFormat(
7043 "refspec wildcard must appear in both source and destination".into(),
7044 ));
7045 }
7046 if refspec.pattern != (src_pattern_count == 1 || dst_pattern_count == 1) {
7047 return Err(GitError::InvalidFormat(
7048 "refspec pattern flag does not match endpoints".into(),
7049 ));
7050 }
7051 Ok(())
7052}
7053
7054fn parse_fetch_head_record(format: ObjectFormat, line: &[u8]) -> Result<FetchHeadRecord> {
7055 validate_fetch_head_line(line)?;
7056 let line = trim_trailing_lf(line);
7057 let mut fields = line.splitn(3, |byte| *byte == b'\t');
7058 let oid = fields
7059 .next()
7060 .ok_or_else(|| GitError::InvalidFormat("FETCH_HEAD record is missing oid".into()))?;
7061 let merge_marker = fields.next().ok_or_else(|| {
7062 GitError::InvalidFormat("FETCH_HEAD record is missing merge marker".into())
7063 })?;
7064 let description = fields.next().ok_or_else(|| {
7065 GitError::InvalidFormat("FETCH_HEAD record is missing description".into())
7066 })?;
7067 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7068 validate_protocol_v2_token("FETCH_HEAD oid", oid)?;
7069 let not_for_merge = match merge_marker {
7070 b"" => false,
7071 b"not-for-merge" => true,
7072 _ => {
7073 return Err(GitError::InvalidFormat(
7074 "FETCH_HEAD record has invalid merge marker".into(),
7075 ));
7076 }
7077 };
7078 let description =
7079 std::str::from_utf8(description).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7080 validate_fetch_head_description_field(description)?;
7081 Ok(FetchHeadRecord {
7082 oid: ObjectId::from_hex(format, oid)?,
7083 not_for_merge,
7084 description: description.to_string(),
7085 })
7086}
7087
7088fn validate_fetch_head_line(value: &[u8]) -> Result<()> {
7089 if value.is_empty() {
7090 return Err(GitError::InvalidFormat("FETCH_HEAD record is empty".into()));
7091 }
7092 if !value.ends_with(b"\n") {
7093 return Err(GitError::InvalidFormat(
7094 "FETCH_HEAD record missing LF".into(),
7095 ));
7096 }
7097 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7098 return Err(GitError::InvalidFormat(
7099 "FETCH_HEAD record contains a delimiter byte".into(),
7100 ));
7101 }
7102 Ok(())
7103}
7104
7105fn validate_fetch_head_description_field(value: &str) -> Result<()> {
7106 if value.is_empty() {
7107 return Err(GitError::InvalidFormat(
7108 "FETCH_HEAD description is empty".into(),
7109 ));
7110 }
7111 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7112 return Err(GitError::InvalidFormat(
7113 "FETCH_HEAD description contains a delimiter byte".into(),
7114 ));
7115 }
7116 Ok(())
7117}
7118
7119fn refspec_is_excluded(negative: &[&RefSpec], source: &str) -> Result<bool> {
7120 for refspec in negative {
7121 if refspec_matches_source(refspec, source)? {
7122 return Ok(true);
7123 }
7124 }
7125 Ok(false)
7126}
7127
7128fn zero_object_id(format: ObjectFormat) -> Result<ObjectId> {
7129 Ok(ObjectId::null(format))
7130}
7131
7132fn local_ref<'a>(refs: &'a [PushSourceRef], name: &str) -> Option<&'a PushSourceRef> {
7133 refs.iter().find(|reference| reference.name == name)
7134}
7135
7136fn remote_ref<'a>(refs: &'a [RefAdvertisement], name: &str) -> Option<&'a RefAdvertisement> {
7137 refs.iter().find(|reference| reference.name == name)
7138}
7139
7140fn validate_push_source_ref(format: ObjectFormat, reference: &PushSourceRef) -> Result<()> {
7141 if reference.oid.format() != format {
7142 return Err(GitError::InvalidObjectId(
7143 "push source ref object format does not match repository".into(),
7144 ));
7145 }
7146 validate_refspec_endpoint("push source ref name", &reference.name)
7147}
7148
7149fn require_receive_pack_feature(advertised: bool, name: &str) -> Result<()> {
7150 if advertised {
7151 Ok(())
7152 } else {
7153 Err(GitError::InvalidFormat(format!(
7154 "receive-pack feature {name} was not advertised"
7155 )))
7156 }
7157}
7158
7159fn validate_smart_http_service(service: GitService) -> Result<()> {
7160 match service {
7161 GitService::UploadPack | GitService::ReceivePack => Ok(()),
7162 GitService::UploadArchive => Err(GitError::InvalidFormat(
7163 "smart HTTP only supports upload-pack and receive-pack services".into(),
7164 )),
7165 }
7166}
7167
7168fn normalize_http_repository_path(path: &str) -> Result<String> {
7169 if path.is_empty() {
7170 return Err(GitError::InvalidFormat(
7171 "smart HTTP repository path is empty".into(),
7172 ));
7173 }
7174 if !path.starts_with('/') {
7175 return Err(GitError::InvalidFormat(
7176 "smart HTTP repository path must start with /".into(),
7177 ));
7178 }
7179 if path
7180 .bytes()
7181 .any(|byte| matches!(byte, b'\n' | b'\r' | 0 | b'?' | b'#'))
7182 {
7183 return Err(GitError::InvalidFormat(
7184 "smart HTTP repository path contains a delimiter byte".into(),
7185 ));
7186 }
7187 let normalized = path.trim_end_matches('/');
7188 Ok(if normalized.is_empty() {
7189 "/".into()
7190 } else {
7191 normalized.to_string()
7192 })
7193}
7194
7195fn dumb_http_pack_resource_path(
7196 repository_path: &str,
7197 hash: &ObjectId,
7198 suffix: &str,
7199) -> Result<String> {
7200 let repository_path = normalize_http_repository_path(repository_path)?;
7201 Ok(format!(
7202 "{repository_path}/objects/pack/pack-{hash}.{suffix}"
7203 ))
7204}
7205
7206fn parse_smart_http_content_type(value: &str, suffix: &str) -> Result<GitService> {
7207 let value = value.trim();
7208 if value.is_empty() {
7209 return Err(GitError::InvalidFormat(
7210 "smart HTTP content type is empty".into(),
7211 ));
7212 }
7213 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7214 return Err(GitError::InvalidFormat(
7215 "smart HTTP content type contains a delimiter byte".into(),
7216 ));
7217 }
7218 let value = value.to_ascii_lowercase();
7219 let service = value
7220 .strip_prefix("application/x-")
7221 .and_then(|value| value.strip_suffix(suffix))
7222 .ok_or_else(|| GitError::InvalidFormat("invalid smart HTTP content type".into()))?;
7223 let service = parse_git_service(service)?;
7224 validate_smart_http_service(service)?;
7225 Ok(service)
7226}
7227
7228fn validate_dumb_http_info_ref_line(value: &[u8]) -> Result<()> {
7229 if value.is_empty() {
7230 return Err(GitError::InvalidFormat(
7231 "dumb HTTP ref record is empty".into(),
7232 ));
7233 }
7234 if !value.ends_with(b"\n") {
7235 return Err(GitError::InvalidFormat(
7236 "dumb HTTP ref record missing LF".into(),
7237 ));
7238 }
7239 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7240 return Err(GitError::InvalidFormat(
7241 "dumb HTTP ref record contains a delimiter byte".into(),
7242 ));
7243 }
7244 Ok(())
7245}
7246
7247fn validate_dumb_http_ref_name(value: &str) -> Result<()> {
7248 validate_protocol_v2_token("dumb HTTP ref name", value)?;
7249 if value.ends_with("^{}") {
7250 return Err(GitError::InvalidFormat(
7251 "dumb HTTP ref name must not include peeled suffix".into(),
7252 ));
7253 }
7254 Ok(())
7255}
7256
7257fn validate_dumb_http_alternate_line(value: &[u8]) -> Result<()> {
7258 if value.is_empty() {
7259 return Err(GitError::InvalidFormat(
7260 "dumb HTTP alternate is empty".into(),
7261 ));
7262 }
7263 if !value.ends_with(b"\n") {
7264 return Err(GitError::InvalidFormat(
7265 "dumb HTTP alternate missing LF".into(),
7266 ));
7267 }
7268 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7269 return Err(GitError::InvalidFormat(
7270 "dumb HTTP alternate contains a delimiter byte".into(),
7271 ));
7272 }
7273 Ok(())
7274}
7275
7276fn validate_dumb_http_alternate(value: &str) -> Result<()> {
7277 if value.is_empty() {
7278 return Err(GitError::InvalidFormat(
7279 "dumb HTTP alternate is empty".into(),
7280 ));
7281 }
7282 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7283 return Err(GitError::InvalidFormat(
7284 "dumb HTTP alternate contains a delimiter byte".into(),
7285 ));
7286 }
7287 Ok(())
7288}
7289
7290fn validate_protocol_v2_token(label: &str, value: &str) -> Result<()> {
7291 if value.is_empty() {
7292 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7293 }
7294 if value
7295 .bytes()
7296 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | 0))
7297 {
7298 return Err(GitError::InvalidFormat(format!(
7299 "{label} contains a delimiter byte"
7300 )));
7301 }
7302 Ok(())
7303}
7304
7305fn validate_protocol_v2_line(label: &str, value: &[u8]) -> Result<()> {
7306 if value.is_empty() {
7307 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7308 }
7309 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7310 return Err(GitError::InvalidFormat(format!(
7311 "{label} contains a delimiter byte"
7312 )));
7313 }
7314 Ok(())
7315}
7316
7317fn parse_protocol_v2_line_text<'a>(label: &str, value: &'a [u8]) -> Result<&'a str> {
7318 validate_protocol_v2_line(label, value)?;
7319 let value = trim_trailing_lf(value);
7320 if value.is_empty() {
7321 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7322 }
7323 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
7324 return Err(GitError::InvalidFormat(format!(
7325 "{label} contains a delimiter byte"
7326 )));
7327 }
7328 std::str::from_utf8(value).map_err(|err| GitError::InvalidFormat(err.to_string()))
7329}
7330
7331fn parse_oid_argument(
7332 format: ObjectFormat,
7333 label: &str,
7334 value: &str,
7335 prefix: &str,
7336) -> Result<ObjectId> {
7337 let oid = value
7338 .strip_prefix(prefix)
7339 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7340 validate_protocol_v2_token(label, oid)?;
7341 ObjectId::from_hex(format, oid)
7342}
7343
7344fn parse_u32_argument(label: &str, value: &str, prefix: &str) -> Result<u32> {
7345 let number = value
7346 .strip_prefix(prefix)
7347 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7348 validate_protocol_v2_token(label, number)?;
7349 let parsed = number
7350 .parse::<u32>()
7351 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7352 if parsed == 0 {
7353 return Err(GitError::InvalidFormat(format!("{label} must be positive")));
7354 }
7355 Ok(parsed)
7356}
7357
7358fn parse_u64_argument(label: &str, value: &str, prefix: &str) -> Result<u64> {
7359 let number = value
7360 .strip_prefix(prefix)
7361 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7362 validate_protocol_v2_token(label, number)?;
7363 number
7364 .parse::<u64>()
7365 .map_err(|err| GitError::InvalidFormat(err.to_string()))
7366}
7367
7368fn line(mut payload: Vec<u8>) -> Vec<u8> {
7369 payload.push(b'\n');
7370 payload
7371}
7372
7373fn line_from_str(payload: &str) -> Vec<u8> {
7374 line(payload.as_bytes().to_vec())
7375}
7376
7377fn trim_trailing_lf(input: &[u8]) -> &[u8] {
7378 input.strip_suffix(b"\n").unwrap_or(input)
7379}
7380
7381#[cfg(test)]
7382mod tests {
7383 use super::*;
7384
7385 #[test]
7386 fn pkt_line_frame_encodes_data_and_control_frames() {
7387 assert_eq!(PktLine(b"hello\n".to_vec()).encode(), b"000ahello\n");
7388 assert_eq!(
7389 PktLineFrame::data(b"hello\n".to_vec())
7390 .expect("test operation should succeed")
7391 .encode(),
7392 b"000ahello\n"
7393 );
7394 assert_eq!(PktLineFrame::Flush.encode(), b"0000");
7395 assert_eq!(PktLineFrame::Delimiter.encode(), b"0001");
7396 assert_eq!(PktLineFrame::ResponseEnd.encode(), b"0002");
7397 assert_eq!(
7398 PktLineFrame::data(b"hello\n".to_vec())
7399 .expect("test operation should succeed")
7400 .try_encode()
7401 .expect("test operation should succeed"),
7402 b"000ahello\n"
7403 );
7404 }
7405
7406 #[test]
7407 fn pkt_line_frame_parses_data_and_control_frames() {
7408 assert_eq!(
7409 PktLineFrame::parse(b"000ahello\n").expect("test operation should succeed"),
7410 (PktLineFrame::Data(b"hello\n".to_vec()), 10)
7411 );
7412 assert_eq!(
7413 PktLineFrame::parse(b"0000").expect("test operation should succeed"),
7414 (PktLineFrame::Flush, 4)
7415 );
7416 assert_eq!(
7417 PktLineFrame::parse(b"0001").expect("test operation should succeed"),
7418 (PktLineFrame::Delimiter, 4)
7419 );
7420 assert_eq!(
7421 PktLineFrame::parse(b"0002").expect("test operation should succeed"),
7422 (PktLineFrame::ResponseEnd, 4)
7423 );
7424 }
7425
7426 #[test]
7427 fn pkt_line_stream_parses_multiple_frames() {
7428 let frames = parse_pkt_line_stream(b"000eversion 2\n00010009done\n0000")
7429 .expect("test operation should succeed");
7430 assert_eq!(
7431 frames,
7432 vec![
7433 PktLineFrame::Data(b"version 2\n".to_vec()),
7434 PktLineFrame::Delimiter,
7435 PktLineFrame::Data(b"done\n".to_vec()),
7436 PktLineFrame::Flush,
7437 ]
7438 );
7439 }
7440
7441 #[test]
7442 fn pkt_line_stream_reads_and_writes_incremental_io() {
7443 let frames = vec![
7444 PktLineFrame::Data(b"version 2\n".to_vec()),
7445 PktLineFrame::Delimiter,
7446 PktLineFrame::Data(b"done\n".to_vec()),
7447 PktLineFrame::Flush,
7448 ];
7449 let mut encoded = Vec::new();
7450 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
7451 assert_eq!(encoded, b"000eversion 2\n00010009done\n0000");
7452 assert_eq!(
7453 read_pkt_line_frames(&mut encoded.as_slice()).expect("test operation should succeed"),
7454 frames
7455 );
7456
7457 let mut empty: &[u8] = b"";
7458 assert_eq!(
7459 read_pkt_line_frame(&mut empty).expect("test operation should succeed"),
7460 None
7461 );
7462 }
7463
7464 #[test]
7465 fn pkt_line_stream_reads_until_control_packets() {
7466 let input = b"000eversion 2\n0000trailing";
7467 let frames = read_pkt_line_frames_until_flush(&mut &input[..])
7468 .expect("test operation should succeed");
7469 assert_eq!(
7470 frames,
7471 vec![
7472 PktLineFrame::Data(b"version 2\n".to_vec()),
7473 PktLineFrame::Flush,
7474 ]
7475 );
7476
7477 let input = b"0009done\n0002next";
7478 let frames = read_pkt_line_frames_until_response_end(&mut &input[..])
7479 .expect("test operation should succeed");
7480 assert_eq!(
7481 frames,
7482 vec![
7483 PktLineFrame::Data(b"done\n".to_vec()),
7484 PktLineFrame::ResponseEnd,
7485 ]
7486 );
7487 assert!(read_pkt_line_frames_until_flush(&mut &b"0009done\n"[..]).is_err());
7488 }
7489
7490 #[test]
7491 fn pkt_line_rejects_invalid_lengths() {
7492 assert!(PktLineFrame::parse(b"000").is_err());
7493 assert!(PktLineFrame::parse(b"0003").is_err());
7494 assert!(PktLineFrame::parse(b"000ahello").is_err());
7495 assert!(PktLineFrame::parse(b"zzzz").is_err());
7496 assert!(read_pkt_line_frame(&mut &b"000"[..]).is_err());
7497 assert!(read_pkt_line_frame(&mut &b"0003"[..]).is_err());
7498 }
7499
7500 #[test]
7501 fn pkt_line_rejects_oversized_data() {
7502 let payload = vec![b'x'; PKT_LINE_MAX_PAYLOAD_LEN + 1];
7503 assert!(PktLineFrame::data(payload.clone()).is_err());
7504 assert!(PktLine(payload.clone()).try_encode().is_err());
7505 assert!(PktLineFrame::Data(payload.clone()).try_encode().is_err());
7506 assert!(write_pkt_line_frame(&mut Vec::new(), &PktLineFrame::Data(payload)).is_err());
7507 assert!(PktLineFrame::parse(b"fff1").is_err());
7508 }
7509
7510 #[test]
7511 fn protocol_error_lines_parse_encode_and_stream() {
7512 let error = parse_error_line(b"ERR remote rejected request\n")
7513 .expect("test operation should succeed");
7514 assert_eq!(
7515 error,
7516 ProtocolErrorLine {
7517 message: "remote rejected request".into(),
7518 }
7519 );
7520 assert_eq!(
7521 encode_error_line(&error).expect("test operation should succeed"),
7522 b"ERR remote rejected request\n"
7523 );
7524 assert_eq!(
7525 parse_error_frame(&PktLineFrame::Data(
7526 b"ERR remote rejected request\n".to_vec()
7527 ))
7528 .expect("test operation should succeed"),
7529 Some(error.clone())
7530 );
7531 assert_eq!(
7532 parse_error_frame(&PktLineFrame::Data(b"NAK\n".to_vec()))
7533 .expect("test operation should succeed"),
7534 None
7535 );
7536
7537 let mut encoded = Vec::new();
7538 write_error_line(&mut encoded, &error).expect("test operation should succeed");
7539 encoded.extend_from_slice(b"tail");
7540 let mut input = encoded.as_slice();
7541 assert_eq!(
7542 read_error_line(&mut input).expect("test operation should succeed"),
7543 error
7544 );
7545 assert_eq!(input, b"tail");
7546 }
7547
7548 #[test]
7549 fn protocol_error_lines_reject_malformed_messages() {
7550 assert!(parse_error_line(b"ERR\n").is_err());
7551 assert!(parse_error_line(b"ERR \n").is_err());
7552 assert!(parse_error_line(b"ERR bad\0message\n").is_err());
7553 assert!(parse_error_line(b"NAK\n").is_err());
7554 assert!(
7555 encode_error_line(&ProtocolErrorLine {
7556 message: "bad\nmessage".into(),
7557 })
7558 .is_err()
7559 );
7560 assert!(read_error_line(&mut &b"0000"[..]).is_err());
7561 }
7562
7563 #[test]
7564 fn refspec_parser_handles_fetch_push_and_negative_forms() {
7565 assert_eq!(
7566 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7567 .expect("test operation should succeed"),
7568 RefSpec {
7569 force: true,
7570 negative: false,
7571 src: Some("refs/heads/*".into()),
7572 dst: Some("refs/remotes/origin/*".into()),
7573 pattern: true,
7574 }
7575 );
7576 assert_eq!(
7577 parse_refspec("refs/heads/main").expect("test operation should succeed"),
7578 RefSpec {
7579 force: false,
7580 negative: false,
7581 src: Some("refs/heads/main".into()),
7582 dst: None,
7583 pattern: false,
7584 }
7585 );
7586 assert_eq!(
7587 parse_refspec(":refs/heads/topic").expect("test operation should succeed"),
7588 RefSpec {
7589 force: false,
7590 negative: false,
7591 src: None,
7592 dst: Some("refs/heads/topic".into()),
7593 pattern: false,
7594 }
7595 );
7596 assert_eq!(
7597 parse_refspec(":").expect("test operation should succeed"),
7598 RefSpec {
7599 force: false,
7600 negative: false,
7601 src: None,
7602 dst: None,
7603 pattern: false,
7604 }
7605 );
7606 assert_eq!(
7607 parse_refspec("^refs/tags/private/*").expect("test operation should succeed"),
7608 RefSpec {
7609 force: false,
7610 negative: true,
7611 src: Some("refs/tags/private/*".into()),
7612 dst: None,
7613 pattern: true,
7614 }
7615 );
7616 }
7617
7618 #[test]
7619 fn refspec_encode_and_map_sources() {
7620 let pattern = parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7621 .expect("test operation should succeed");
7622 assert_eq!(
7623 encode_refspec(&pattern).expect("test operation should succeed"),
7624 "+refs/heads/*:refs/remotes/origin/*"
7625 );
7626 assert!(
7627 refspec_matches_source(&pattern, "refs/heads/main")
7628 .expect("test operation should succeed")
7629 );
7630 assert_eq!(
7631 refspec_map_source(&pattern, "refs/heads/main").expect("test operation should succeed"),
7632 Some("refs/remotes/origin/main".into())
7633 );
7634 assert_eq!(
7635 refspec_map_source(&pattern, "refs/tags/v1").expect("test operation should succeed"),
7636 None
7637 );
7638
7639 let direct = parse_refspec("HEAD:refs/heads/main").expect("test operation should succeed");
7640 assert_eq!(
7641 encode_refspec(&direct).expect("test operation should succeed"),
7642 "HEAD:refs/heads/main"
7643 );
7644 assert_eq!(
7645 refspec_map_source(&direct, "HEAD").expect("test operation should succeed"),
7646 Some("refs/heads/main".into())
7647 );
7648
7649 let delete = parse_refspec(":refs/heads/old").expect("test operation should succeed");
7650 assert_eq!(
7651 encode_refspec(&delete).expect("test operation should succeed"),
7652 ":refs/heads/old"
7653 );
7654 assert_eq!(
7655 refspec_map_source(&delete, "HEAD").expect("test operation should succeed"),
7656 None
7657 );
7658
7659 let matching = parse_refspec(":").expect("test operation should succeed");
7660 assert_eq!(
7661 encode_refspec(&matching).expect("test operation should succeed"),
7662 ":"
7663 );
7664 }
7665
7666 #[test]
7667 fn refspec_parser_rejects_malformed_values() {
7668 assert!(parse_refspec("").is_err());
7669 assert!(parse_refspec("+^refs/heads/main").is_err());
7670 assert!(parse_refspec("^refs/heads/main:refs/remotes/origin/main").is_err());
7671 assert!(parse_refspec("refs/heads/*:refs/remotes/origin/main").is_err());
7672 assert!(parse_refspec("refs/heads/**:refs/remotes/origin/*").is_err());
7673 assert!(parse_refspec("refs/heads/main:refs/remotes/origin/main:extra").is_err());
7674 assert!(parse_refspec("refs/heads/main\n").is_err());
7675 assert!(
7676 encode_refspec(&RefSpec {
7677 force: false,
7678 negative: false,
7679 src: Some("refs/heads/*".into()),
7680 dst: Some("refs/remotes/origin/main".into()),
7681 pattern: true,
7682 })
7683 .is_err()
7684 );
7685 }
7686
7687 #[test]
7688 fn fetch_head_records_parse_encode_and_describe_refs() {
7689 let first = ObjectId::from_hex(
7690 ObjectFormat::Sha1,
7691 "1111111111111111111111111111111111111111",
7692 )
7693 .expect("test operation should succeed");
7694 let second = ObjectId::from_hex(
7695 ObjectFormat::Sha1,
7696 "2222222222222222222222222222222222222222",
7697 )
7698 .expect("test operation should succeed");
7699 let input = b"1111111111111111111111111111111111111111\t\tbranch 'main' of ../bundle.bdl\n2222222222222222222222222222222222222222\tnot-for-merge\ttag 'v1' of ../bundle.bdl\n";
7700 let records =
7701 parse_fetch_head(ObjectFormat::Sha1, input).expect("test operation should succeed");
7702 assert_eq!(
7703 records,
7704 vec![
7705 FetchHeadRecord {
7706 oid: first,
7707 not_for_merge: false,
7708 description: "branch 'main' of ../bundle.bdl".into(),
7709 },
7710 FetchHeadRecord {
7711 oid: second,
7712 not_for_merge: true,
7713 description: "tag 'v1' of ../bundle.bdl".into(),
7714 },
7715 ]
7716 );
7717 assert_eq!(
7718 encode_fetch_head(&records).expect("test operation should succeed"),
7719 input
7720 );
7721 assert_eq!(
7722 parse_fetch_head(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
7723 Vec::<FetchHeadRecord>::new()
7724 );
7725 assert_eq!(
7726 fetch_head_remote_description("refs/heads/main", "../bundle.bdl")
7727 .expect("test operation should succeed"),
7728 "branch 'main' of ../bundle.bdl"
7729 );
7730 assert_eq!(
7731 fetch_head_remote_description("refs/tags/v1", "../bundle.bdl")
7732 .expect("test operation should succeed"),
7733 "tag 'v1' of ../bundle.bdl"
7734 );
7735 assert_eq!(
7737 fetch_head_remote_description("HEAD", "../bundle.bdl")
7738 .expect("test operation should succeed"),
7739 "../bundle.bdl"
7740 );
7741 }
7742
7743 #[test]
7744 fn fetch_head_records_streams_round_trip() {
7745 let records = vec![FetchHeadRecord {
7746 oid: ObjectId::from_hex(
7747 ObjectFormat::Sha1,
7748 "1111111111111111111111111111111111111111",
7749 )
7750 .expect("test operation should succeed"),
7751 not_for_merge: false,
7752 description: "branch 'main' of ../bundle.bdl".into(),
7753 }];
7754 let mut encoded = Vec::new();
7755 write_fetch_head(&mut encoded, &records).expect("test operation should succeed");
7756 let mut input = encoded.as_slice();
7757 assert_eq!(
7758 read_fetch_head(ObjectFormat::Sha1, &mut input).expect("test operation should succeed"),
7759 records
7760 );
7761 assert!(input.is_empty());
7762 }
7763
7764 #[test]
7765 fn fetch_head_records_reject_malformed_lines() {
7766 assert!(
7767 parse_fetch_head(
7768 ObjectFormat::Sha1,
7769 b"1111111111111111111111111111111111111111\t\tbranch 'main'"
7770 )
7771 .is_err()
7772 );
7773 assert!(
7774 parse_fetch_head(
7775 ObjectFormat::Sha1,
7776 b"1111111111111111111111111111111111111111\tfor-merge\tbranch 'main'\n"
7777 )
7778 .is_err()
7779 );
7780 assert!(parse_fetch_head(ObjectFormat::Sha1, b"not-a-hash\t\tbranch 'main'\n").is_err());
7781 assert!(
7782 encode_fetch_head(&[FetchHeadRecord {
7783 oid: ObjectId::from_hex(
7784 ObjectFormat::Sha1,
7785 "1111111111111111111111111111111111111111"
7786 )
7787 .expect("test operation should succeed"),
7788 not_for_merge: false,
7789 description: "bad\ndescription".into(),
7790 }])
7791 .is_err()
7792 );
7793 }
7794
7795 #[test]
7796 fn fetch_planner_maps_direct_pattern_and_negative_refspecs() {
7797 let main = ObjectId::from_hex(
7798 ObjectFormat::Sha1,
7799 "1111111111111111111111111111111111111111",
7800 )
7801 .expect("test operation should succeed");
7802 let next = ObjectId::from_hex(
7803 ObjectFormat::Sha1,
7804 "2222222222222222222222222222222222222222",
7805 )
7806 .expect("test operation should succeed");
7807 let refs = vec![
7808 RefAdvertisement {
7809 oid: main.clone(),
7810 name: "refs/heads/main".into(),
7811 capabilities: Vec::new(),
7812 },
7813 RefAdvertisement {
7814 oid: next.clone(),
7815 name: "refs/heads/tmp".into(),
7816 capabilities: Vec::new(),
7817 },
7818 ];
7819 let refspecs = vec![
7820 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7821 .expect("test operation should succeed"),
7822 parse_refspec("^refs/heads/tmp").expect("test operation should succeed"),
7823 ];
7824 assert_eq!(
7825 plan_fetch_ref_updates(&refs, &refspecs, false).expect("test operation should succeed"),
7826 vec![FetchRefUpdate {
7827 src: "refs/heads/main".into(),
7828 dst: Some("refs/remotes/origin/main".into()),
7829 oid: main,
7830 not_for_merge: false,
7831 force: true,
7832 }]
7833 );
7834 }
7835
7836 #[test]
7837 fn fetch_planner_autofollows_tags_and_builds_fetch_head_records() {
7838 let commit = ObjectId::from_hex(
7839 ObjectFormat::Sha1,
7840 "1111111111111111111111111111111111111111",
7841 )
7842 .expect("test operation should succeed");
7843 let refs = vec![
7844 RefAdvertisement {
7845 oid: commit.clone(),
7846 name: "refs/heads/main".into(),
7847 capabilities: Vec::new(),
7848 },
7849 RefAdvertisement {
7850 oid: commit.clone(),
7851 name: "refs/tags/v1".into(),
7852 capabilities: Vec::new(),
7853 },
7854 ];
7855 let refspecs = vec![
7856 parse_refspec("refs/heads/main:refs/heads/main")
7857 .expect("test operation should succeed"),
7858 ];
7859 let updates =
7860 plan_fetch_ref_updates(&refs, &refspecs, true).expect("test operation should succeed");
7861 assert_eq!(
7862 updates,
7863 vec![
7864 FetchRefUpdate {
7865 src: "refs/heads/main".into(),
7866 dst: Some("refs/heads/main".into()),
7867 oid: commit.clone(),
7868 not_for_merge: false,
7869 force: false,
7870 },
7871 FetchRefUpdate {
7872 src: "refs/tags/v1".into(),
7873 dst: Some("refs/tags/v1".into()),
7874 oid: commit.clone(),
7875 not_for_merge: true,
7876 force: false,
7877 },
7878 ]
7879 );
7880 assert_eq!(
7881 fetch_ref_updates_to_fetch_head(&updates, "../bundle.bdl")
7882 .expect("test operation should succeed"),
7883 vec![
7884 FetchHeadRecord {
7885 oid: commit.clone(),
7886 not_for_merge: false,
7887 description: "branch 'main' of ../bundle.bdl".into(),
7888 },
7889 FetchHeadRecord {
7890 oid: commit,
7891 not_for_merge: true,
7892 description: "tag 'v1' of ../bundle.bdl".into(),
7893 },
7894 ]
7895 );
7896 }
7897
7898 #[test]
7899 fn fetch_planner_rejects_missing_or_sourceless_refspecs() {
7900 let refs = vec![RefAdvertisement {
7901 oid: ObjectId::from_hex(
7902 ObjectFormat::Sha1,
7903 "1111111111111111111111111111111111111111",
7904 )
7905 .expect("test operation should succeed"),
7906 name: "refs/heads/main".into(),
7907 capabilities: Vec::new(),
7908 }];
7909 assert!(
7910 plan_fetch_ref_updates(
7911 &refs,
7912 &[parse_refspec("refs/heads/missing").expect("test operation should succeed")],
7913 false
7914 )
7915 .is_err()
7916 );
7917 assert!(
7918 plan_fetch_ref_updates(
7919 &refs,
7920 &[parse_refspec(":refs/heads/main").expect("test operation should succeed")],
7921 false
7922 )
7923 .is_err()
7924 );
7925 }
7926
7927 #[test]
7928 fn fetch_planner_sourceless_positive_refspec_returns_err_not_panic() {
7929 let refs = vec![RefAdvertisement {
7934 oid: ObjectId::from_hex(
7935 ObjectFormat::Sha1,
7936 "1111111111111111111111111111111111111111",
7937 )
7938 .expect("test operation should succeed"),
7939 name: "refs/heads/main".into(),
7940 capabilities: Vec::new(),
7941 }];
7942 let malformed = RefSpec {
7943 force: false,
7944 negative: false,
7945 src: None,
7946 dst: Some("refs/heads/main".into()),
7947 pattern: false,
7948 };
7949 let result = plan_fetch_ref_updates(&refs, &[malformed], false);
7950 assert!(
7951 result.is_err(),
7952 "sourceless positive refspec must yield Err, got {result:?}"
7953 );
7954 }
7955
7956 #[test]
7957 fn push_planner_builds_create_update_delete_and_matching_commands() {
7958 let old = ObjectId::from_hex(
7959 ObjectFormat::Sha1,
7960 "1111111111111111111111111111111111111111",
7961 )
7962 .expect("test operation should succeed");
7963 let new = ObjectId::from_hex(
7964 ObjectFormat::Sha1,
7965 "2222222222222222222222222222222222222222",
7966 )
7967 .expect("test operation should succeed");
7968 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
7969 let local_refs = vec![
7970 PushSourceRef {
7971 oid: new.clone(),
7972 name: "refs/heads/main".into(),
7973 },
7974 PushSourceRef {
7975 oid: new.clone(),
7976 name: "refs/heads/new".into(),
7977 },
7978 ];
7979 let remote_refs = vec![
7980 RefAdvertisement {
7981 oid: old.clone(),
7982 name: "refs/heads/main".into(),
7983 capabilities: Vec::new(),
7984 },
7985 RefAdvertisement {
7986 oid: old.clone(),
7987 name: "refs/heads/old".into(),
7988 capabilities: Vec::new(),
7989 },
7990 ];
7991
7992 assert_eq!(
7993 plan_push_commands(
7994 ObjectFormat::Sha1,
7995 &local_refs,
7996 &remote_refs,
7997 &[parse_refspec("refs/heads/main").expect("test operation should succeed")],
7998 )
7999 .expect("test operation should succeed"),
8000 vec![ReceivePackCommand {
8001 old_id: old.clone(),
8002 new_id: new.clone(),
8003 name: "refs/heads/main".into(),
8004 }]
8005 );
8006 assert_eq!(
8007 plan_push_commands(
8008 ObjectFormat::Sha1,
8009 &local_refs,
8010 &remote_refs,
8011 &[parse_refspec("refs/heads/new:refs/heads/new")
8012 .expect("test operation should succeed")],
8013 )
8014 .expect("test operation should succeed"),
8015 vec![ReceivePackCommand {
8016 old_id: zero.clone(),
8017 new_id: new.clone(),
8018 name: "refs/heads/new".into(),
8019 }]
8020 );
8021 assert_eq!(
8022 plan_push_commands(
8023 ObjectFormat::Sha1,
8024 &local_refs,
8025 &remote_refs,
8026 &[parse_refspec(":refs/heads/old").expect("test operation should succeed")],
8027 )
8028 .expect("test operation should succeed"),
8029 vec![ReceivePackCommand {
8030 old_id: old.clone(),
8031 new_id: zero,
8032 name: "refs/heads/old".into(),
8033 }]
8034 );
8035 assert_eq!(
8036 plan_push_commands(
8037 ObjectFormat::Sha1,
8038 &local_refs,
8039 &remote_refs,
8040 &[parse_refspec(":").expect("test operation should succeed")],
8041 )
8042 .expect("test operation should succeed"),
8043 vec![ReceivePackCommand {
8044 old_id: old,
8045 new_id: new,
8046 name: "refs/heads/main".into(),
8047 }]
8048 );
8049 }
8050
8051 #[test]
8052 fn push_planner_builds_wildcard_commands_and_rejects_bad_refspecs() {
8053 let new = ObjectId::from_hex(
8054 ObjectFormat::Sha1,
8055 "2222222222222222222222222222222222222222",
8056 )
8057 .expect("test operation should succeed");
8058 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8059 let local_refs = vec![PushSourceRef {
8060 oid: new.clone(),
8061 name: "refs/heads/topic".into(),
8062 }];
8063 let commands = plan_push_commands(
8064 ObjectFormat::Sha1,
8065 &local_refs,
8066 &[],
8067 &[parse_refspec("refs/heads/*:refs/heads/review/*")
8068 .expect("test operation should succeed")],
8069 )
8070 .expect("test operation should succeed");
8071 assert_eq!(
8072 commands,
8073 vec![ReceivePackCommand {
8074 old_id: zero,
8075 new_id: new,
8076 name: "refs/heads/review/topic".into(),
8077 }]
8078 );
8079 assert!(
8080 plan_push_commands(
8081 ObjectFormat::Sha1,
8082 &local_refs,
8083 &[],
8084 &[parse_refspec("^refs/heads/topic").expect("test operation should succeed")],
8085 )
8086 .is_err()
8087 );
8088 assert_eq!(
8089 plan_push_commands(
8090 ObjectFormat::Sha1,
8091 &local_refs,
8092 &[],
8093 &[parse_refspec(":refs/heads/missing").expect("test operation should succeed")],
8094 )
8095 .expect("missing deletes are sent as zero-to-zero commands"),
8096 vec![ReceivePackCommand {
8097 old_id: zero,
8098 new_id: zero,
8099 name: "refs/heads/missing".into(),
8100 }]
8101 );
8102 }
8103
8104 #[test]
8105 fn receive_pack_push_request_builder_negotiates_capabilities() {
8106 let old_id = ObjectId::from_hex(
8107 ObjectFormat::Sha1,
8108 "1111111111111111111111111111111111111111",
8109 )
8110 .expect("test operation should succeed");
8111 let new_id = ObjectId::from_hex(
8112 ObjectFormat::Sha1,
8113 "2222222222222222222222222222222222222222",
8114 )
8115 .expect("test operation should succeed");
8116 let features = ReceivePackFeatures {
8117 report_status_v2: true,
8118 atomic: true,
8119 ofs_delta: true,
8120 push_options: true,
8121 side_band_64k: true,
8122 quiet: true,
8123 object_format: Some(ObjectFormat::Sha1),
8124 ..ReceivePackFeatures::default()
8125 };
8126 let request = build_receive_pack_push_request(
8127 &features,
8128 vec![ReceivePackCommand {
8129 old_id,
8130 new_id,
8131 name: "refs/heads/main".into(),
8132 }],
8133 b"PACKdata".to_vec(),
8134 ReceivePackPushRequestOptions {
8135 report_status_v2: true,
8136 atomic: true,
8137 ofs_delta: true,
8138 side_band_64k: true,
8139 quiet: true,
8140 agent: Some("sley/0".into()),
8141 object_format: Some(ObjectFormat::Sha1),
8142 push_options: vec!["ci.skip".into()],
8143 ..ReceivePackPushRequestOptions::default()
8144 },
8145 )
8146 .expect("test operation should succeed");
8147 assert_eq!(
8148 request.commands.capabilities,
8149 vec![
8150 Capability {
8151 name: "report-status-v2".into(),
8152 value: None,
8153 },
8154 Capability {
8155 name: "atomic".into(),
8156 value: None,
8157 },
8158 Capability {
8159 name: "ofs-delta".into(),
8160 value: None,
8161 },
8162 Capability {
8163 name: "side-band-64k".into(),
8164 value: None,
8165 },
8166 Capability {
8167 name: "quiet".into(),
8168 value: None,
8169 },
8170 Capability {
8171 name: "agent".into(),
8172 value: Some("sley/0".into()),
8173 },
8174 Capability {
8175 name: "object-format".into(),
8176 value: Some("sha1".into()),
8177 },
8178 Capability {
8179 name: "push-options".into(),
8180 value: None,
8181 },
8182 ]
8183 );
8184 assert_eq!(request.push_options, Some(vec!["ci.skip".into()]));
8185 validate_receive_pack_push_request_features(&features, &request)
8186 .expect("test operation should succeed");
8187 }
8188
8189 #[test]
8190 fn receive_pack_push_request_builder_handles_delete_only_and_rejects_unadvertised() {
8191 let old_id = ObjectId::from_hex(
8192 ObjectFormat::Sha1,
8193 "1111111111111111111111111111111111111111",
8194 )
8195 .expect("test operation should succeed");
8196 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8197 let features = ReceivePackFeatures {
8198 delete_refs: true,
8199 ..ReceivePackFeatures::default()
8200 };
8201 let request = build_receive_pack_push_request(
8202 &features,
8203 vec![ReceivePackCommand {
8204 old_id,
8205 new_id: zero,
8206 name: "refs/heads/old".into(),
8207 }],
8208 Vec::new(),
8209 ReceivePackPushRequestOptions::default(),
8210 )
8211 .expect("test operation should succeed");
8212 assert_eq!(
8213 request.commands.capabilities,
8214 vec![Capability {
8215 name: "delete-refs".into(),
8216 value: None,
8217 }]
8218 );
8219 assert!(request.packfile.is_empty());
8220
8221 assert!(
8222 build_receive_pack_push_request(
8223 &ReceivePackFeatures::default(),
8224 request.commands.commands.clone(),
8225 Vec::new(),
8226 ReceivePackPushRequestOptions::default(),
8227 )
8228 .is_err()
8229 );
8230 assert!(
8231 build_receive_pack_push_request(
8232 &features,
8233 request.commands.commands,
8234 b"PACK".to_vec(),
8235 ReceivePackPushRequestOptions::default(),
8236 )
8237 .is_err()
8238 );
8239 assert!(
8240 build_receive_pack_push_request(
8241 &features,
8242 Vec::new(),
8243 Vec::new(),
8244 ReceivePackPushRequestOptions {
8245 push_options: vec!["ci.skip".into()],
8246 ..ReceivePackPushRequestOptions::default()
8247 },
8248 )
8249 .is_err()
8250 );
8251 }
8252
8253 #[test]
8254 fn smart_http_helpers_build_paths_and_content_types() {
8255 let sha1 = ObjectId::from_hex(
8256 ObjectFormat::Sha1,
8257 "1111111111111111111111111111111111111111",
8258 )
8259 .expect("test operation should succeed");
8260 let sha256 = ObjectId::from_hex(
8261 ObjectFormat::Sha256,
8262 "2222222222222222222222222222222222222222222222222222222222222222",
8263 )
8264 .expect("test operation should succeed");
8265 assert_eq!(
8266 smart_http_info_refs_path("/repo.git/", GitService::UploadPack)
8267 .expect("test operation should succeed"),
8268 "/repo.git/info/refs?service=git-upload-pack"
8269 );
8270 assert_eq!(
8271 dumb_http_info_refs_path("/repo.git/").expect("test operation should succeed"),
8272 "/repo.git/info/refs"
8273 );
8274 assert_eq!(
8275 dumb_http_alternates_path("/repo.git").expect("test operation should succeed"),
8276 "/repo.git/objects/info/http-alternates"
8277 );
8278 assert_eq!(
8279 dumb_http_packs_path("/repo.git/").expect("test operation should succeed"),
8280 "/repo.git/objects/info/packs"
8281 );
8282 assert_eq!(
8283 dumb_http_loose_object_path("/repo.git/", &sha1)
8284 .expect("test operation should succeed"),
8285 "/repo.git/objects/11/11111111111111111111111111111111111111"
8286 );
8287 assert_eq!(
8288 dumb_http_loose_object_path("/repo.git/", &sha256)
8289 .expect("test operation should succeed"),
8290 "/repo.git/objects/22/22222222222222222222222222222222222222222222222222222222222222"
8291 );
8292 assert_eq!(
8293 dumb_http_pack_file_path("/repo.git/", &sha1).expect("test operation should succeed"),
8294 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.pack"
8295 );
8296 assert_eq!(
8297 dumb_http_pack_index_path("/repo.git/", &sha1).expect("test operation should succeed"),
8298 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.idx"
8299 );
8300 assert_eq!(
8301 smart_http_rpc_path("/repo.git", GitService::ReceivePack)
8302 .expect("test operation should succeed"),
8303 "/repo.git/git-receive-pack"
8304 );
8305 assert_eq!(
8306 smart_http_advertisement_content_type(GitService::UploadPack)
8307 .expect("test operation should succeed"),
8308 "application/x-git-upload-pack-advertisement"
8309 );
8310 assert_eq!(
8311 smart_http_rpc_request_content_type(GitService::UploadPack)
8312 .expect("test operation should succeed"),
8313 "application/x-git-upload-pack-request"
8314 );
8315 assert_eq!(
8316 smart_http_rpc_result_content_type(GitService::ReceivePack)
8317 .expect("test operation should succeed"),
8318 "application/x-git-receive-pack-result"
8319 );
8320 assert_eq!(
8321 parse_smart_http_advertisement_content_type(
8322 "Application/X-Git-Upload-Pack-Advertisement"
8323 )
8324 .expect("test operation should succeed"),
8325 GitService::UploadPack
8326 );
8327 assert_eq!(
8328 parse_smart_http_rpc_request_content_type("application/x-git-receive-pack-request")
8329 .expect("test operation should succeed"),
8330 GitService::ReceivePack
8331 );
8332 assert_eq!(
8333 parse_smart_http_rpc_result_content_type("application/x-git-upload-pack-result")
8334 .expect("test operation should succeed"),
8335 GitService::UploadPack
8336 );
8337 }
8338
8339 #[test]
8340 fn smart_http_helpers_reject_invalid_services_paths_and_content_types() {
8341 let oid = ObjectId::from_hex(
8342 ObjectFormat::Sha1,
8343 "1111111111111111111111111111111111111111",
8344 )
8345 .expect("test operation should succeed");
8346 assert!(smart_http_info_refs_path("repo.git", GitService::UploadPack).is_err());
8347 assert!(smart_http_rpc_path("/repo.git?x=1", GitService::UploadPack).is_err());
8348 assert!(dumb_http_info_refs_path("repo.git").is_err());
8349 assert!(dumb_http_alternates_path("/repo.git#fragment").is_err());
8350 assert!(dumb_http_packs_path("/repo.git?query").is_err());
8351 assert!(dumb_http_loose_object_path("repo.git", &oid).is_err());
8352 assert!(dumb_http_pack_file_path("/repo.git#fragment", &oid).is_err());
8353 assert!(dumb_http_pack_index_path("/repo.git?query", &oid).is_err());
8354 assert!(smart_http_info_refs_path("/repo.git", GitService::UploadArchive).is_err());
8355 assert!(smart_http_advertisement_content_type(GitService::UploadArchive).is_err());
8356 assert!(
8357 parse_smart_http_advertisement_content_type(
8358 "application/x-git-upload-archive-advertisement"
8359 )
8360 .is_err()
8361 );
8362 assert!(
8363 parse_smart_http_rpc_request_content_type("application/x-git-upload-pack-result")
8364 .is_err()
8365 );
8366 assert!(
8367 parse_smart_http_rpc_result_content_type(
8368 "application/x-git-receive-pack-result; charset=utf-8"
8369 )
8370 .is_err()
8371 );
8372 }
8373
8374 #[test]
8375 fn sideband_packets_parse_and_encode_channels() {
8376 let payloads = vec![
8377 b"\x01PACK bytes".to_vec(),
8378 b"\x02counting objects\n".to_vec(),
8379 b"\x03fatal error\n".to_vec(),
8380 ];
8381 let packets = parse_sideband_packets(&payloads).expect("test operation should succeed");
8382 assert_eq!(
8383 packets,
8384 vec![
8385 SideBandPacket {
8386 channel: SideBandChannel::Data,
8387 data: b"PACK bytes".to_vec(),
8388 },
8389 SideBandPacket {
8390 channel: SideBandChannel::Progress,
8391 data: b"counting objects\n".to_vec(),
8392 },
8393 SideBandPacket {
8394 channel: SideBandChannel::Fatal,
8395 data: b"fatal error\n".to_vec(),
8396 },
8397 ]
8398 );
8399 assert_eq!(
8400 encode_sideband_packets(&packets).expect("test operation should succeed"),
8401 payloads
8402 );
8403 }
8404
8405 #[test]
8406 fn sideband_stream_parses_encodes_and_demuxes_packets() {
8407 let frames = vec![
8408 PktLineFrame::Data(vec![1, b'P', b'A']),
8409 PktLineFrame::Data(vec![2, b'c', b'o', b'u', b'n', b't', b'\n']),
8410 PktLineFrame::Data(vec![1, b'C', b'K']),
8411 PktLineFrame::Flush,
8412 ];
8413 let packets = parse_sideband_stream(&frames).expect("test operation should succeed");
8414 assert_eq!(
8415 packets,
8416 vec![
8417 SideBandPacket {
8418 channel: SideBandChannel::Data,
8419 data: b"PA".to_vec(),
8420 },
8421 SideBandPacket {
8422 channel: SideBandChannel::Progress,
8423 data: b"count\n".to_vec(),
8424 },
8425 SideBandPacket {
8426 channel: SideBandChannel::Data,
8427 data: b"CK".to_vec(),
8428 },
8429 ]
8430 );
8431 assert_eq!(
8432 encode_sideband_stream(&packets).expect("test operation should succeed"),
8433 frames
8434 );
8435 assert_eq!(
8436 demux_sideband_stream(&frames).expect("test operation should succeed"),
8437 SideBandDemux {
8438 data: b"PACK".to_vec(),
8439 progress: vec![b"count\n".to_vec()],
8440 }
8441 );
8442 }
8443
8444 #[test]
8445 fn sideband_stream_reads_and_writes_until_flush() {
8446 let packets = vec![
8447 SideBandPacket {
8448 channel: SideBandChannel::Data,
8449 data: b"PACK".to_vec(),
8450 },
8451 SideBandPacket {
8452 channel: SideBandChannel::Progress,
8453 data: b"done\n".to_vec(),
8454 },
8455 ];
8456 let mut encoded = Vec::new();
8457 write_sideband_stream(&mut encoded, &packets).expect("test operation should succeed");
8458 encoded.extend_from_slice(b"tail");
8459
8460 let mut input = encoded.as_slice();
8461 assert_eq!(
8462 read_sideband_stream(&mut input).expect("test operation should succeed"),
8463 packets
8464 );
8465 assert_eq!(input, b"tail");
8466
8467 let mut input = encoded.as_slice();
8468 assert_eq!(
8469 read_and_demux_sideband_stream(&mut input).expect("test operation should succeed"),
8470 SideBandDemux {
8471 data: b"PACK".to_vec(),
8472 progress: vec![b"done\n".to_vec()],
8473 }
8474 );
8475 assert_eq!(input, b"tail");
8476 }
8477
8478 #[test]
8479 fn sideband_packets_demux_data_and_progress() {
8480 let payloads = vec![
8481 b"\x01PACK".to_vec(),
8482 b"\x02counting objects\n".to_vec(),
8483 b"\x01 bytes".to_vec(),
8484 b"\x02done\n".to_vec(),
8485 ];
8486 assert_eq!(
8487 parse_and_demux_sideband_packets(&payloads).expect("test operation should succeed"),
8488 SideBandDemux {
8489 data: b"PACK bytes".to_vec(),
8490 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
8491 }
8492 );
8493 }
8494
8495 #[test]
8496 fn sideband_packets_reject_bad_channels_and_oversize_payloads() {
8497 assert!(parse_sideband_packet(b"").is_err());
8498 assert!(parse_sideband_packet(b"\x04bad").is_err());
8499 assert!(
8500 parse_sideband_stream(&[PktLineFrame::Data(vec![1, b'P', b'A', b'C', b'K'])]).is_err()
8501 );
8502 assert!(parse_sideband_stream(&[PktLineFrame::Delimiter, PktLineFrame::Flush]).is_err());
8503 assert!(
8504 parse_sideband_stream(&[
8505 PktLineFrame::Data(vec![1, b'P', b'A']),
8506 PktLineFrame::Flush,
8507 PktLineFrame::Data(vec![1, b'C', b'K']),
8508 ])
8509 .is_err()
8510 );
8511 assert!(
8512 parse_sideband_stream(&[
8513 PktLineFrame::Data(vec![1, b'P', b'A']),
8514 PktLineFrame::Data(b"\x04bad".to_vec()),
8515 PktLineFrame::Flush,
8516 ])
8517 .is_err()
8518 );
8519 assert!(
8520 encode_sideband_packet(&SideBandPacket {
8521 channel: SideBandChannel::Data,
8522 data: vec![0; PKT_LINE_MAX_PAYLOAD_LEN],
8523 })
8524 .is_err()
8525 );
8526 assert!(
8527 demux_sideband_packets(&[SideBandPacket {
8528 channel: SideBandChannel::Fatal,
8529 data: b"remote died\n".to_vec(),
8530 }])
8531 .is_err()
8532 );
8533 }
8534
8535 #[test]
8536 fn upload_archive_request_parses_and_encodes_arguments() {
8537 let frames = vec![
8538 PktLineFrame::Data(b"argument --format=tar\n".to_vec()),
8539 PktLineFrame::Data(b"argument HEAD:dir with spaces\n".to_vec()),
8540 PktLineFrame::Flush,
8541 ];
8542 let request = parse_upload_archive_request(&frames).expect("test operation should succeed");
8543 assert_eq!(
8544 request,
8545 UploadArchiveRequest {
8546 arguments: vec!["--format=tar".into(), "HEAD:dir with spaces".into()],
8547 }
8548 );
8549 assert_eq!(
8550 encode_upload_archive_request(&request).expect("test operation should succeed"),
8551 frames
8552 );
8553 }
8554
8555 #[test]
8556 fn upload_archive_request_streams_round_trip() {
8557 let request = UploadArchiveRequest {
8558 arguments: vec!["--prefix=src/".into(), "main".into()],
8559 };
8560 let mut encoded = Vec::new();
8561 write_upload_archive_request(&mut encoded, &request)
8562 .expect("test operation should succeed");
8563 encoded.extend_from_slice(b"tail");
8564
8565 let mut input = encoded.as_slice();
8566 assert_eq!(
8567 read_upload_archive_request(&mut input).expect("test operation should succeed"),
8568 request
8569 );
8570 assert_eq!(input, b"tail");
8571 }
8572
8573 #[test]
8574 fn upload_archive_request_rejects_malformed_streams() {
8575 assert!(parse_upload_archive_request(&[PktLineFrame::Flush]).is_err());
8576 assert!(
8577 parse_upload_archive_request(&[
8578 PktLineFrame::Data(b"--format=tar\n".to_vec()),
8579 PktLineFrame::Flush,
8580 ])
8581 .is_err()
8582 );
8583 assert!(
8584 parse_upload_archive_request(&[
8585 PktLineFrame::Data(b"argument HEAD\n".to_vec()),
8586 PktLineFrame::Delimiter,
8587 PktLineFrame::Flush,
8588 ])
8589 .is_err()
8590 );
8591 assert!(
8592 encode_upload_archive_request(&UploadArchiveRequest {
8593 arguments: vec!["bad\narg".into()],
8594 })
8595 .is_err()
8596 );
8597 }
8598
8599 #[test]
8600 fn upload_archive_response_parses_ack_sideband_and_nack() {
8601 let ack_frames = vec![
8602 PktLineFrame::Data(b"ACK\n".to_vec()),
8603 PktLineFrame::Data(b"\x01tar bytes".to_vec()),
8604 PktLineFrame::Data(b"\x02progress\n".to_vec()),
8605 PktLineFrame::Flush,
8606 ];
8607 let response =
8608 parse_upload_archive_response(&ack_frames).expect("test operation should succeed");
8609 assert_eq!(
8610 response,
8611 UploadArchiveResponse::Ack {
8612 sideband: vec![
8613 SideBandPacket {
8614 channel: SideBandChannel::Data,
8615 data: b"tar bytes".to_vec(),
8616 },
8617 SideBandPacket {
8618 channel: SideBandChannel::Progress,
8619 data: b"progress\n".to_vec(),
8620 },
8621 ],
8622 }
8623 );
8624 assert_eq!(
8625 encode_upload_archive_response(&response).expect("test operation should succeed"),
8626 ack_frames
8627 );
8628 assert_eq!(
8629 demux_upload_archive_response(&response).expect("test operation should succeed"),
8630 SideBandDemux {
8631 data: b"tar bytes".to_vec(),
8632 progress: vec![b"progress\n".to_vec()],
8633 }
8634 );
8635
8636 let nack = UploadArchiveResponse::Nack {
8637 message: "unreachable tree".into(),
8638 };
8639 let nack_frames = vec![
8640 PktLineFrame::Data(b"NACK unreachable tree\n".to_vec()),
8641 PktLineFrame::Flush,
8642 ];
8643 assert_eq!(
8644 parse_upload_archive_response(&nack_frames).expect("test operation should succeed"),
8645 nack
8646 );
8647 assert_eq!(
8648 encode_upload_archive_response(&nack).expect("test operation should succeed"),
8649 nack_frames
8650 );
8651 assert!(demux_upload_archive_response(&nack).is_err());
8652 }
8653
8654 #[test]
8655 fn upload_archive_response_streams_round_trip() {
8656 let response = UploadArchiveResponse::Ack {
8657 sideband: vec![SideBandPacket {
8658 channel: SideBandChannel::Data,
8659 data: b"tar bytes".to_vec(),
8660 }],
8661 };
8662 let mut encoded = Vec::new();
8663 write_upload_archive_response(&mut encoded, &response)
8664 .expect("test operation should succeed");
8665 encoded.extend_from_slice(b"tail");
8666
8667 let mut input = encoded.as_slice();
8668 assert_eq!(
8669 read_upload_archive_response(&mut input).expect("test operation should succeed"),
8670 response
8671 );
8672 assert_eq!(input, b"tail");
8673 }
8674
8675 #[test]
8676 fn upload_archive_response_rejects_malformed_streams() {
8677 assert!(parse_upload_archive_response(&[]).is_err());
8678 assert!(
8679 parse_upload_archive_response(&[
8680 PktLineFrame::Data(b"ACK\n".to_vec()),
8681 PktLineFrame::Flush,
8682 PktLineFrame::Data(b"\x01tail".to_vec()),
8683 ])
8684 .is_err()
8685 );
8686 assert!(
8687 parse_upload_archive_response(&[
8688 PktLineFrame::Data(b"NACK\n".to_vec()),
8689 PktLineFrame::Flush,
8690 ])
8691 .is_err()
8692 );
8693 assert!(
8694 parse_upload_archive_response(&[
8695 PktLineFrame::Data(b"NACK denied\n".to_vec()),
8696 PktLineFrame::Data(b"\x02extra\n".to_vec()),
8697 PktLineFrame::Flush,
8698 ])
8699 .is_err()
8700 );
8701 assert!(
8702 encode_upload_archive_response(&UploadArchiveResponse::Nack {
8703 message: "bad\nmessage".into(),
8704 })
8705 .is_err()
8706 );
8707 }
8708
8709 #[test]
8710 fn capabilities_parse_and_encode_tokens() {
8711 let capabilities = parse_capabilities(
8712 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main\n",
8713 )
8714 .expect("test operation should succeed");
8715 assert_eq!(
8716 capabilities,
8717 vec![
8718 Capability {
8719 name: "multi_ack".into(),
8720 value: None,
8721 },
8722 Capability {
8723 name: "thin-pack".into(),
8724 value: None,
8725 },
8726 Capability {
8727 name: "agent".into(),
8728 value: Some("git/2.54.0".into()),
8729 },
8730 Capability {
8731 name: "symref".into(),
8732 value: Some("HEAD:refs/heads/main".into()),
8733 },
8734 ]
8735 );
8736 assert_eq!(
8737 encode_capabilities(&capabilities).expect("test operation should succeed"),
8738 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main"
8739 );
8740 }
8741
8742 #[test]
8743 fn capabilities_reject_empty_or_delimited_fields() {
8744 assert!(parse_capabilities(b"multi_ack thin-pack").is_err());
8745 assert!(parse_capabilities(b"agent=").is_err());
8746 assert!(parse_capabilities(b"symref=HEAD:refs/heads/main\nbad").is_err());
8747 assert!(
8748 encode_capabilities(&[Capability {
8749 name: "bad name".into(),
8750 value: None,
8751 }])
8752 .is_err()
8753 );
8754 }
8755
8756 #[test]
8757 fn protocol_v2_object_format_uses_capability_or_defaults_to_sha1() {
8758 assert_eq!(
8759 protocol_v2_object_format(&[]).expect("test operation should succeed"),
8760 ObjectFormat::Sha1
8761 );
8762 assert_eq!(
8763 protocol_v2_object_format(&[Capability {
8764 name: "object-format".into(),
8765 value: Some("sha256".into()),
8766 }])
8767 .expect("test operation should succeed"),
8768 ObjectFormat::Sha256
8769 );
8770 assert!(
8771 protocol_v2_object_format(&[Capability {
8772 name: "object-format".into(),
8773 value: None,
8774 }])
8775 .is_err()
8776 );
8777 assert!(
8778 protocol_v2_object_format(&[
8779 Capability {
8780 name: "object-format".into(),
8781 value: Some("sha1".into()),
8782 },
8783 Capability {
8784 name: "object-format".into(),
8785 value: Some("sha256".into()),
8786 },
8787 ])
8788 .is_err()
8789 );
8790 assert!(
8791 protocol_v2_object_format(&[Capability {
8792 name: "object-format".into(),
8793 value: Some("unknown".into()),
8794 }])
8795 .is_err()
8796 );
8797 }
8798
8799 #[test]
8800 fn protocol_v2_command_request_capabilities_validate_against_handshake() {
8801 let handshake = TransportHandshake {
8802 protocol: ProtocolVersion::V2,
8803 capabilities: vec![
8804 Capability {
8805 name: "fetch".into(),
8806 value: Some("shallow filter".into()),
8807 },
8808 Capability {
8809 name: "agent".into(),
8810 value: Some("sley/0".into()),
8811 },
8812 Capability {
8813 name: "object-format".into(),
8814 value: Some("sha1".into()),
8815 },
8816 ],
8817 };
8818 validate_protocol_v2_command_request_capabilities(
8819 &handshake,
8820 &ProtocolV2CommandRequest {
8821 command: "fetch".into(),
8822 capabilities: vec![
8823 Capability {
8824 name: "agent".into(),
8825 value: Some("client/1".into()),
8826 },
8827 Capability {
8828 name: "object-format".into(),
8829 value: Some("sha1".into()),
8830 },
8831 ],
8832 arguments: Vec::new(),
8833 },
8834 )
8835 .expect("test operation should succeed");
8836 assert!(
8837 validate_protocol_v2_command_request_capabilities(
8838 &handshake,
8839 &ProtocolV2CommandRequest {
8840 command: "ls-refs".into(),
8841 capabilities: Vec::new(),
8842 arguments: Vec::new(),
8843 },
8844 )
8845 .is_err()
8846 );
8847 assert!(
8848 validate_protocol_v2_command_request_capabilities(
8849 &handshake,
8850 &ProtocolV2CommandRequest {
8851 command: "fetch".into(),
8852 capabilities: vec![Capability {
8853 name: "server-option".into(),
8854 value: None,
8855 }],
8856 arguments: Vec::new(),
8857 },
8858 )
8859 .is_err()
8860 );
8861 assert!(
8862 validate_protocol_v2_command_request_capabilities(
8863 &handshake,
8864 &ProtocolV2CommandRequest {
8865 command: "fetch".into(),
8866 capabilities: vec![Capability {
8867 name: "object-format".into(),
8868 value: Some("sha256".into()),
8869 }],
8870 arguments: Vec::new(),
8871 },
8872 )
8873 .is_err()
8874 );
8875 assert!(
8876 validate_protocol_v2_command_request_capabilities(
8877 &handshake,
8878 &ProtocolV2CommandRequest {
8879 command: "fetch".into(),
8880 capabilities: vec![Capability {
8881 name: "agent".into(),
8882 value: None,
8883 }],
8884 arguments: Vec::new(),
8885 },
8886 )
8887 .is_err()
8888 );
8889 }
8890
8891 #[test]
8892 fn protocol_v2_command_options_parse_and_encode_known_capabilities() {
8893 let capabilities = vec![
8894 Capability {
8895 name: "agent".into(),
8896 value: Some("sley/0".into()),
8897 },
8898 Capability {
8899 name: "object-format".into(),
8900 value: Some("sha256".into()),
8901 },
8902 Capability {
8903 name: "server-option".into(),
8904 value: Some("trace=true".into()),
8905 },
8906 Capability {
8907 name: "server-option".into(),
8908 value: Some("region=west".into()),
8909 },
8910 Capability {
8911 name: "session-id".into(),
8912 value: Some("abc123".into()),
8913 },
8914 ];
8915 let options = parse_protocol_v2_command_options(&capabilities)
8916 .expect("test operation should succeed");
8917 assert_eq!(
8918 options,
8919 ProtocolV2CommandOptions {
8920 agent: Some("sley/0".into()),
8921 object_format: Some(ObjectFormat::Sha256),
8922 server_options: vec!["trace=true".into(), "region=west".into()],
8923 extra: vec![Capability {
8924 name: "session-id".into(),
8925 value: Some("abc123".into()),
8926 }],
8927 }
8928 );
8929 assert_eq!(
8930 encode_protocol_v2_command_options(&options).expect("test operation should succeed"),
8931 capabilities
8932 );
8933 }
8934
8935 #[test]
8936 fn protocol_v2_command_options_reject_malformed_known_capabilities() {
8937 assert!(
8938 parse_protocol_v2_command_options(&[
8939 Capability {
8940 name: "agent".into(),
8941 value: Some("sley/0".into()),
8942 },
8943 Capability {
8944 name: "agent".into(),
8945 value: Some("sley/1".into()),
8946 },
8947 ])
8948 .is_err()
8949 );
8950 assert!(
8951 parse_protocol_v2_command_options(&[Capability {
8952 name: "object-format".into(),
8953 value: Some("sha512".into()),
8954 }])
8955 .is_err()
8956 );
8957 assert!(
8958 parse_protocol_v2_command_options(&[Capability {
8959 name: "server-option".into(),
8960 value: None,
8961 }])
8962 .is_err()
8963 );
8964 assert!(
8965 encode_protocol_v2_command_options(&ProtocolV2CommandOptions {
8966 extra: vec![Capability {
8967 name: "server-option".into(),
8968 value: Some("trace=true".into()),
8969 }],
8970 ..ProtocolV2CommandOptions::default()
8971 })
8972 .is_err()
8973 );
8974 }
8975
8976 #[test]
8977 fn protocol_v2_ls_refs_features_parse_and_encode_advertisement() {
8978 let capabilities = vec![Capability {
8979 name: "ls-refs".into(),
8980 value: Some("unborn custom".into()),
8981 }];
8982 let features = parse_protocol_v2_ls_refs_features(&capabilities)
8983 .expect("test operation should succeed")
8984 .expect("test operation should succeed");
8985 assert_eq!(
8986 features,
8987 ProtocolV2LsRefsFeatures {
8988 unborn: true,
8989 unknown: vec!["custom".into()],
8990 }
8991 );
8992 assert_eq!(
8993 encode_protocol_v2_ls_refs_capability(&features)
8994 .expect("test operation should succeed"),
8995 capabilities[0]
8996 );
8997 assert_eq!(
8998 parse_protocol_v2_ls_refs_features(&[Capability {
8999 name: "ls-refs".into(),
9000 value: None,
9001 }])
9002 .expect("test operation should succeed")
9003 .expect("test operation should succeed"),
9004 ProtocolV2LsRefsFeatures::default()
9005 );
9006 assert!(
9007 parse_protocol_v2_ls_refs_features(&[Capability {
9008 name: "fetch".into(),
9009 value: Some("filter".into()),
9010 }])
9011 .expect("test operation should succeed")
9012 .is_none()
9013 );
9014 }
9015
9016 #[test]
9017 fn protocol_v2_ls_refs_features_reject_malformed_advertisements() {
9018 assert!(
9019 parse_protocol_v2_ls_refs_features(&[
9020 Capability {
9021 name: "ls-refs".into(),
9022 value: None,
9023 },
9024 Capability {
9025 name: "ls-refs".into(),
9026 value: None,
9027 },
9028 ])
9029 .is_err()
9030 );
9031 assert!(
9032 parse_protocol_v2_ls_refs_features(&[Capability {
9033 name: "ls-refs".into(),
9034 value: Some("unborn custom".into()),
9035 }])
9036 .is_err()
9037 );
9038 assert!(
9039 encode_protocol_v2_ls_refs_capability(&ProtocolV2LsRefsFeatures {
9040 unknown: vec!["unborn".into()],
9041 ..ProtocolV2LsRefsFeatures::default()
9042 })
9043 .is_err()
9044 );
9045 }
9046
9047 #[test]
9048 fn protocol_v2_ls_refs_command_request_validates_unborn_feature() {
9049 let handshake = TransportHandshake {
9050 protocol: ProtocolVersion::V2,
9051 capabilities: vec![Capability {
9052 name: "ls-refs".into(),
9053 value: Some("unborn".into()),
9054 }],
9055 };
9056 let request = ProtocolV2CommandRequest {
9057 command: "ls-refs".into(),
9058 capabilities: Vec::new(),
9059 arguments: vec![b"unborn".to_vec(), b"ref-prefix HEAD".to_vec()],
9060 };
9061 let parsed = validate_protocol_v2_ls_refs_command_request(&handshake, &request)
9062 .expect("test operation should succeed");
9063 assert!(parsed.unborn);
9064 assert_eq!(parsed.ref_prefixes, vec!["HEAD"]);
9065
9066 let blocked = TransportHandshake {
9067 protocol: ProtocolVersion::V2,
9068 capabilities: vec![Capability {
9069 name: "ls-refs".into(),
9070 value: None,
9071 }],
9072 };
9073 assert!(validate_protocol_v2_ls_refs_command_request(&blocked, &request).is_err());
9074 }
9075
9076 #[test]
9077 fn protocol_v2_fetch_features_parse_and_encode_advertisement() {
9078 let capabilities = vec![Capability {
9079 name: "fetch".into(),
9080 value: Some(
9081 "shallow wait-for-done filter ref-in-want sideband-all packfile-uris custom".into(),
9082 ),
9083 }];
9084 let features = parse_protocol_v2_fetch_features(&capabilities)
9085 .expect("test operation should succeed")
9086 .expect("test operation should succeed");
9087 assert_eq!(
9088 features,
9089 ProtocolV2FetchFeatures {
9090 shallow: true,
9091 wait_for_done: true,
9092 filter: true,
9093 ref_in_want: true,
9094 sideband_all: true,
9095 packfile_uris: true,
9096 unknown: vec!["custom".into()],
9097 }
9098 );
9099 assert_eq!(
9100 encode_protocol_v2_fetch_capability(&features).expect("test operation should succeed"),
9101 capabilities[0]
9102 );
9103 assert_eq!(
9104 parse_protocol_v2_fetch_features(&[Capability {
9105 name: "fetch".into(),
9106 value: None,
9107 }])
9108 .expect("test operation should succeed")
9109 .expect("test operation should succeed"),
9110 ProtocolV2FetchFeatures::default()
9111 );
9112 assert!(
9113 parse_protocol_v2_fetch_features(&[])
9114 .expect("test operation should succeed")
9115 .is_none()
9116 );
9117 }
9118
9119 #[test]
9120 fn protocol_v2_fetch_features_reject_malformed_advertisements() {
9121 assert!(
9122 parse_protocol_v2_fetch_features(&[
9123 Capability {
9124 name: "fetch".into(),
9125 value: None,
9126 },
9127 Capability {
9128 name: "fetch".into(),
9129 value: None,
9130 },
9131 ])
9132 .is_err()
9133 );
9134 assert!(
9135 parse_protocol_v2_fetch_features(&[Capability {
9136 name: "fetch".into(),
9137 value: Some("filter shallow".into()),
9138 }])
9139 .is_err()
9140 );
9141 assert!(
9142 encode_protocol_v2_fetch_capability(&ProtocolV2FetchFeatures {
9143 unknown: vec!["filter".into()],
9144 ..ProtocolV2FetchFeatures::default()
9145 })
9146 .is_err()
9147 );
9148 }
9149
9150 #[test]
9151 fn protocol_v2_fetch_request_features_validate_feature_gated_arguments() {
9152 let features = ProtocolV2FetchFeatures {
9153 shallow: true,
9154 wait_for_done: true,
9155 filter: true,
9156 ref_in_want: true,
9157 sideband_all: true,
9158 packfile_uris: true,
9159 unknown: Vec::new(),
9160 };
9161 validate_protocol_v2_fetch_request_features(
9162 &features,
9163 &ProtocolV2FetchRequest {
9164 want_refs: vec!["refs/heads/main".into()],
9165 shallow: vec![
9166 ObjectId::from_hex(
9167 ObjectFormat::Sha1,
9168 "1111111111111111111111111111111111111111",
9169 )
9170 .expect("test operation should succeed"),
9171 ],
9172 deepen: Some(1),
9173 filter: Some("blob:none".into()),
9174 packfile_uris: Some("https".into()),
9175 sideband_all: true,
9176 wait_for_done: true,
9177 ..ProtocolV2FetchRequest::default()
9178 },
9179 )
9180 .expect("test operation should succeed");
9181
9182 let request = ProtocolV2FetchRequest {
9183 want_refs: vec!["refs/heads/main".into()],
9184 filter: Some("blob:none".into()),
9185 sideband_all: true,
9186 ..ProtocolV2FetchRequest::default()
9187 };
9188 assert!(
9189 validate_protocol_v2_fetch_request_features(
9190 &ProtocolV2FetchFeatures::default(),
9191 &request,
9192 )
9193 .is_err()
9194 );
9195 assert!(
9196 validate_protocol_v2_fetch_request_features(
9197 &ProtocolV2FetchFeatures {
9198 ref_in_want: true,
9199 filter: true,
9200 ..ProtocolV2FetchFeatures::default()
9201 },
9202 &request,
9203 )
9204 .is_err()
9205 );
9206 }
9207
9208 #[test]
9209 fn protocol_v2_fetch_command_request_validates_against_handshake_features() {
9210 let handshake = TransportHandshake {
9211 protocol: ProtocolVersion::V2,
9212 capabilities: vec![
9213 Capability {
9214 name: "fetch".into(),
9215 value: Some("filter ref-in-want".into()),
9216 },
9217 Capability {
9218 name: "agent".into(),
9219 value: Some("sley/0".into()),
9220 },
9221 ],
9222 };
9223 let request = ProtocolV2CommandRequest {
9224 command: "fetch".into(),
9225 capabilities: vec![Capability {
9226 name: "agent".into(),
9227 value: Some("client/1".into()),
9228 }],
9229 arguments: vec![
9230 b"want-ref refs/heads/main".to_vec(),
9231 b"filter blob:none".to_vec(),
9232 ],
9233 };
9234 let fetch =
9235 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &request)
9236 .expect("test operation should succeed");
9237 assert_eq!(fetch.want_refs, vec!["refs/heads/main"]);
9238 assert_eq!(fetch.filter.as_deref(), Some("blob:none"));
9239
9240 let mut bad = request.clone();
9241 bad.arguments.push(b"sideband-all".to_vec());
9242 assert!(
9243 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &bad)
9244 .is_err()
9245 );
9246 }
9247
9248 #[test]
9249 fn protocol_v2_object_info_request_parses_encodes_and_validates() {
9250 let oid = ObjectId::from_hex(
9251 ObjectFormat::Sha1,
9252 "1111111111111111111111111111111111111111",
9253 )
9254 .expect("test operation should succeed");
9255 let request = ProtocolV2CommandRequest {
9256 command: "object-info".into(),
9257 capabilities: Vec::new(),
9258 arguments: vec![
9259 b"size".to_vec(),
9260 b"oid 1111111111111111111111111111111111111111".to_vec(),
9261 ],
9262 };
9263 let parsed =
9264 ProtocolV2ObjectInfoRequest::from_command_request(ObjectFormat::Sha1, &request)
9265 .expect("test operation should succeed");
9266 assert_eq!(
9267 parsed,
9268 ProtocolV2ObjectInfoRequest {
9269 size: true,
9270 oids: vec![oid],
9271 }
9272 );
9273 assert_eq!(
9274 parsed
9275 .to_command_request()
9276 .expect("test operation should succeed"),
9277 request
9278 );
9279
9280 let handshake = TransportHandshake {
9281 protocol: ProtocolVersion::V2,
9282 capabilities: vec![Capability {
9283 name: "object-info".into(),
9284 value: None,
9285 }],
9286 };
9287 assert_eq!(
9288 validate_protocol_v2_object_info_command_request(
9289 &handshake,
9290 ObjectFormat::Sha1,
9291 &request,
9292 )
9293 .expect("test operation should succeed"),
9294 parsed
9295 );
9296 }
9297
9298 #[test]
9299 fn protocol_v2_object_info_request_streams_round_trip() {
9300 let request = ProtocolV2ObjectInfoRequest {
9301 size: true,
9302 oids: vec![
9303 ObjectId::from_hex(
9304 ObjectFormat::Sha1,
9305 "1111111111111111111111111111111111111111",
9306 )
9307 .expect("test operation should succeed"),
9308 ],
9309 };
9310 let mut encoded = Vec::new();
9311 write_protocol_v2_object_info_request(&mut encoded, &request)
9312 .expect("test operation should succeed");
9313 encoded.extend_from_slice(b"tail");
9314
9315 let mut input = encoded.as_slice();
9316 assert_eq!(
9317 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut input)
9318 .expect("test operation should succeed"),
9319 request
9320 );
9321 assert_eq!(input, b"tail");
9322 }
9323
9324 #[test]
9325 fn protocol_v2_object_info_request_rejects_malformed_arguments() {
9326 assert!(
9327 ProtocolV2ObjectInfoRequest::from_command_request(
9328 ObjectFormat::Sha1,
9329 &ProtocolV2CommandRequest {
9330 command: "object-info".into(),
9331 capabilities: Vec::new(),
9332 arguments: vec![b"oid 1111111111111111111111111111111111111111".to_vec()],
9333 },
9334 )
9335 .is_err()
9336 );
9337 assert!(
9338 ProtocolV2ObjectInfoRequest::from_command_request(
9339 ObjectFormat::Sha1,
9340 &ProtocolV2CommandRequest {
9341 command: "object-info".into(),
9342 capabilities: Vec::new(),
9343 arguments: vec![b"size".to_vec(), b"size".to_vec()],
9344 },
9345 )
9346 .is_err()
9347 );
9348 assert!(
9349 ProtocolV2ObjectInfoRequest::from_command_request(
9350 ObjectFormat::Sha1,
9351 &ProtocolV2CommandRequest {
9352 command: "object-info".into(),
9353 capabilities: Vec::new(),
9354 arguments: vec![b"size".to_vec()],
9355 },
9356 )
9357 .is_err()
9358 );
9359 assert!(
9360 ProtocolV2ObjectInfoRequest::from_command_request(
9361 ObjectFormat::Sha1,
9362 &ProtocolV2CommandRequest {
9363 command: "object-info".into(),
9364 capabilities: Vec::new(),
9365 arguments: vec![b"size".to_vec(), b"oid not-an-oid".to_vec()],
9366 },
9367 )
9368 .is_err()
9369 );
9370 assert!(
9371 validate_protocol_v2_object_info_command_request(
9372 &TransportHandshake {
9373 protocol: ProtocolVersion::V2,
9374 capabilities: Vec::new(),
9375 },
9376 ObjectFormat::Sha1,
9377 &ProtocolV2CommandRequest {
9378 command: "object-info".into(),
9379 capabilities: Vec::new(),
9380 arguments: vec![
9381 b"size".to_vec(),
9382 b"oid 1111111111111111111111111111111111111111".to_vec(),
9383 ],
9384 },
9385 )
9386 .is_err()
9387 );
9388 }
9389
9390 #[test]
9391 fn protocol_v2_command_request_classifies_known_and_unknown_commands() {
9392 let handshake = TransportHandshake {
9393 protocol: ProtocolVersion::V2,
9394 capabilities: vec![
9395 Capability {
9396 name: "ls-refs".into(),
9397 value: Some("unborn".into()),
9398 },
9399 Capability {
9400 name: "fetch".into(),
9401 value: Some("filter ref-in-want".into()),
9402 },
9403 Capability {
9404 name: "object-info".into(),
9405 value: None,
9406 },
9407 Capability {
9408 name: "server-option".into(),
9409 value: None,
9410 },
9411 Capability {
9412 name: "server-info".into(),
9413 value: Some("custom".into()),
9414 },
9415 ],
9416 };
9417 assert_eq!(
9418 classify_protocol_v2_command_request(
9419 &handshake,
9420 ObjectFormat::Sha1,
9421 &ProtocolV2CommandRequest {
9422 command: "ls-refs".into(),
9423 capabilities: Vec::new(),
9424 arguments: vec![b"unborn".to_vec()],
9425 },
9426 )
9427 .expect("test operation should succeed"),
9428 ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9429 unborn: true,
9430 ..ProtocolV2LsRefsRequest::default()
9431 })
9432 );
9433 assert_eq!(
9434 classify_protocol_v2_command_request(
9435 &handshake,
9436 ObjectFormat::Sha1,
9437 &ProtocolV2CommandRequest {
9438 command: "fetch".into(),
9439 capabilities: Vec::new(),
9440 arguments: vec![
9441 b"want-ref refs/heads/main".to_vec(),
9442 b"filter blob:none".to_vec(),
9443 ],
9444 },
9445 )
9446 .expect("test operation should succeed"),
9447 ProtocolV2Command::Fetch(ProtocolV2FetchRequest {
9448 want_refs: vec!["refs/heads/main".into()],
9449 filter: Some("blob:none".into()),
9450 ..ProtocolV2FetchRequest::default()
9451 })
9452 );
9453 assert_eq!(
9454 classify_protocol_v2_command_request(
9455 &handshake,
9456 ObjectFormat::Sha1,
9457 &ProtocolV2CommandRequest {
9458 command: "object-info".into(),
9459 capabilities: Vec::new(),
9460 arguments: vec![
9461 b"size".to_vec(),
9462 b"oid 1111111111111111111111111111111111111111".to_vec(),
9463 ],
9464 },
9465 )
9466 .expect("test operation should succeed"),
9467 ProtocolV2Command::ObjectInfo(ProtocolV2ObjectInfoRequest {
9468 size: true,
9469 oids: vec![
9470 ObjectId::from_hex(
9471 ObjectFormat::Sha1,
9472 "1111111111111111111111111111111111111111",
9473 )
9474 .expect("test operation should succeed")
9475 ],
9476 })
9477 );
9478
9479 let unknown = ProtocolV2CommandRequest {
9480 command: "server-info".into(),
9481 capabilities: vec![Capability {
9482 name: "server-option".into(),
9483 value: Some("trace=true".into()),
9484 }],
9485 arguments: Vec::new(),
9486 };
9487 assert_eq!(
9488 classify_protocol_v2_command_request(&handshake, ObjectFormat::Sha1, &unknown)
9489 .expect("test operation should succeed"),
9490 ProtocolV2Command::Unknown(unknown)
9491 );
9492 assert!(
9493 classify_protocol_v2_command_request(
9494 &handshake,
9495 ObjectFormat::Sha1,
9496 &ProtocolV2CommandRequest {
9497 command: "not-advertised".into(),
9498 capabilities: Vec::new(),
9499 arguments: Vec::new(),
9500 },
9501 )
9502 .is_err()
9503 );
9504 }
9505
9506 #[test]
9507 fn protocol_v2_session_request_classifies_streamed_command_and_done() {
9508 let handshake = TransportHandshake {
9509 protocol: ProtocolVersion::V2,
9510 capabilities: vec![
9511 Capability {
9512 name: "ls-refs".into(),
9513 value: Some("unborn".into()),
9514 },
9515 Capability {
9516 name: "fetch".into(),
9517 value: Some("filter ref-in-want".into()),
9518 },
9519 ],
9520 };
9521 let command = ProtocolV2Request::Command(ProtocolV2CommandRequest {
9522 command: "ls-refs".into(),
9523 capabilities: Vec::new(),
9524 arguments: vec![b"unborn".to_vec()],
9525 });
9526 assert_eq!(
9527 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &command)
9528 .expect("test operation should succeed"),
9529 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9530 unborn: true,
9531 ..ProtocolV2LsRefsRequest::default()
9532 }))
9533 );
9534 assert_eq!(
9535 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &ProtocolV2Request::Done)
9536 .expect("test operation should succeed"),
9537 ProtocolV2SessionRequest::Done
9538 );
9539
9540 let mut encoded = Vec::new();
9541 write_protocol_v2_request(&mut encoded, &command).expect("test operation should succeed");
9542 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
9543 .expect("test operation should succeed");
9544 encoded.extend_from_slice(b"tail");
9545
9546 let mut input = encoded.as_slice();
9547 assert_eq!(
9548 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9549 .expect("test operation should succeed"),
9550 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9551 unborn: true,
9552 ..ProtocolV2LsRefsRequest::default()
9553 }))
9554 );
9555 assert_eq!(
9556 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9557 .expect("test operation should succeed"),
9558 ProtocolV2SessionRequest::Done
9559 );
9560 assert_eq!(input, b"tail");
9561 }
9562
9563 #[test]
9564 fn advertised_ref_parses_first_v0_capability_line() {
9565 let payload =
9566 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 HEAD\0multi_ack symref=HEAD:refs/heads/main\n";
9567 let advertisement = parse_ref_advertisement(ObjectFormat::Sha1, payload)
9568 .expect("test operation should succeed");
9569 assert_eq!(
9570 advertisement.oid,
9571 ObjectId::from_hex(
9572 ObjectFormat::Sha1,
9573 "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
9574 )
9575 .expect("test operation should succeed")
9576 );
9577 assert_eq!(advertisement.name, "HEAD");
9578 assert_eq!(
9579 advertisement.capabilities,
9580 vec![
9581 Capability {
9582 name: "multi_ack".into(),
9583 value: None,
9584 },
9585 Capability {
9586 name: "symref".into(),
9587 value: Some("HEAD:refs/heads/main".into()),
9588 },
9589 ]
9590 );
9591 }
9592
9593 #[test]
9594 fn advertised_ref_parses_lines_without_capabilities() {
9595 let advertisement = parse_ref_advertisement(
9596 ObjectFormat::Sha1,
9597 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 refs/heads/main\n",
9598 )
9599 .expect("test operation should succeed");
9600 assert_eq!(advertisement.name, "refs/heads/main");
9601 assert!(advertisement.capabilities.is_empty());
9602 }
9603
9604 #[test]
9605 fn advertised_ref_rejects_malformed_payloads() {
9606 assert!(
9607 parse_ref_advertisement(ObjectFormat::Sha1, b"not-an-oid refs/heads/main\n").is_err()
9608 );
9609 assert!(
9610 parse_ref_advertisement(
9611 ObjectFormat::Sha1,
9612 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n"
9613 )
9614 .is_err()
9615 );
9616 }
9617
9618 #[test]
9619 fn advertised_refs_parse_and_encode_stream() {
9620 let main = ObjectId::from_hex(
9621 ObjectFormat::Sha1,
9622 "1111111111111111111111111111111111111111",
9623 )
9624 .expect("test operation should succeed");
9625 let feature = ObjectId::from_hex(
9626 ObjectFormat::Sha1,
9627 "2222222222222222222222222222222222222222",
9628 )
9629 .expect("test operation should succeed");
9630 let frames = vec![
9631 PktLineFrame::Data(
9632 b"1111111111111111111111111111111111111111 HEAD\0multi_ack thin-pack agent=git/2.54.0\n"
9633 .to_vec(),
9634 ),
9635 PktLineFrame::Data(
9636 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9637 ),
9638 PktLineFrame::Flush,
9639 ];
9640 let advertisements = parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9641 .expect("test operation should succeed");
9642 assert_eq!(
9643 advertisements,
9644 vec![
9645 RefAdvertisement {
9646 oid: main,
9647 name: "HEAD".into(),
9648 capabilities: vec![
9649 Capability {
9650 name: "multi_ack".into(),
9651 value: None,
9652 },
9653 Capability {
9654 name: "thin-pack".into(),
9655 value: None,
9656 },
9657 Capability {
9658 name: "agent".into(),
9659 value: Some("git/2.54.0".into()),
9660 },
9661 ],
9662 },
9663 RefAdvertisement {
9664 oid: feature,
9665 name: "refs/heads/feature".into(),
9666 capabilities: Vec::new(),
9667 },
9668 ]
9669 );
9670 assert_eq!(
9671 encode_ref_advertisements(&advertisements).expect("test operation should succeed"),
9672 frames
9673 );
9674 assert_eq!(
9675 parse_ref_advertisements(ObjectFormat::Sha1, &[PktLineFrame::Flush])
9676 .expect("test operation should succeed"),
9677 Vec::<RefAdvertisement>::new()
9678 );
9679 }
9680
9681 #[test]
9682 fn advertised_ref_set_parses_v1_version_refs_and_shallow() {
9683 let main = ObjectId::from_hex(
9684 ObjectFormat::Sha1,
9685 "1111111111111111111111111111111111111111",
9686 )
9687 .expect("test operation should succeed");
9688 let feature = ObjectId::from_hex(
9689 ObjectFormat::Sha1,
9690 "2222222222222222222222222222222222222222",
9691 )
9692 .expect("test operation should succeed");
9693 let shallow = ObjectId::from_hex(
9694 ObjectFormat::Sha1,
9695 "3333333333333333333333333333333333333333",
9696 )
9697 .expect("test operation should succeed");
9698 let frames = vec![
9699 PktLineFrame::Data(b"version 1\n".to_vec()),
9700 PktLineFrame::Data(
9701 b"1111111111111111111111111111111111111111 HEAD\0multi_ack symref=HEAD:refs/heads/main\n"
9702 .to_vec(),
9703 ),
9704 PktLineFrame::Data(
9705 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9706 ),
9707 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
9708 PktLineFrame::Flush,
9709 ];
9710
9711 let set = parse_ref_advertisement_set(ObjectFormat::Sha1, &frames)
9712 .expect("test operation should succeed");
9713 assert_eq!(set.protocol, ProtocolVersion::V1);
9714 assert_eq!(set.shallow, vec![shallow]);
9715 assert_eq!(
9716 set.refs,
9717 vec![
9718 RefAdvertisement {
9719 oid: main,
9720 name: "HEAD".into(),
9721 capabilities: vec![
9722 Capability {
9723 name: "multi_ack".into(),
9724 value: None,
9725 },
9726 Capability {
9727 name: "symref".into(),
9728 value: Some("HEAD:refs/heads/main".into()),
9729 },
9730 ],
9731 },
9732 RefAdvertisement {
9733 oid: feature,
9734 name: "refs/heads/feature".into(),
9735 capabilities: Vec::new(),
9736 },
9737 ]
9738 );
9739 assert_eq!(
9740 parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9741 .expect("test operation should succeed"),
9742 set.refs
9743 );
9744 assert_eq!(
9745 encode_ref_advertisement_set(&set).expect("test operation should succeed"),
9746 frames
9747 );
9748 }
9749
9750 #[test]
9751 fn advertised_refs_streams_round_trip() {
9752 let advertisements = vec![RefAdvertisement {
9753 oid: ObjectId::from_hex(
9754 ObjectFormat::Sha1,
9755 "1111111111111111111111111111111111111111",
9756 )
9757 .expect("test operation should succeed"),
9758 name: "HEAD".into(),
9759 capabilities: vec![Capability {
9760 name: "symref".into(),
9761 value: Some("HEAD:refs/heads/main".into()),
9762 }],
9763 }];
9764 let mut encoded = Vec::new();
9765 write_ref_advertisements(&mut encoded, &advertisements)
9766 .expect("test operation should succeed");
9767 encoded.extend_from_slice(b"tail");
9768
9769 let mut input = encoded.as_slice();
9770 assert_eq!(
9771 read_ref_advertisements(ObjectFormat::Sha1, &mut input)
9772 .expect("test operation should succeed"),
9773 advertisements
9774 );
9775 assert_eq!(input, b"tail");
9776 }
9777
9778 #[test]
9779 fn advertised_ref_set_streams_round_trip() {
9780 let set = RefAdvertisementSet {
9781 protocol: ProtocolVersion::V1,
9782 refs: vec![RefAdvertisement {
9783 oid: ObjectId::from_hex(
9784 ObjectFormat::Sha1,
9785 "1111111111111111111111111111111111111111",
9786 )
9787 .expect("test operation should succeed"),
9788 name: "HEAD".into(),
9789 capabilities: vec![Capability {
9790 name: "symref".into(),
9791 value: Some("HEAD:refs/heads/main".into()),
9792 }],
9793 }],
9794 shallow: vec![
9795 ObjectId::from_hex(
9796 ObjectFormat::Sha1,
9797 "2222222222222222222222222222222222222222",
9798 )
9799 .expect("test operation should succeed"),
9800 ],
9801 };
9802 let mut encoded = Vec::new();
9803 write_ref_advertisement_set(&mut encoded, &set).expect("test operation should succeed");
9804 encoded.extend_from_slice(b"tail");
9805
9806 let mut input = encoded.as_slice();
9807 assert_eq!(
9808 read_ref_advertisement_set(ObjectFormat::Sha1, &mut input)
9809 .expect("test operation should succeed"),
9810 set
9811 );
9812 assert_eq!(input, b"tail");
9813 }
9814
9815 #[test]
9816 fn advertised_refs_reject_malformed_streams() {
9817 assert!(
9818 parse_ref_advertisements(
9819 ObjectFormat::Sha1,
9820 &[PktLineFrame::Data(
9821 b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),
9822 )],
9823 )
9824 .is_err()
9825 );
9826 assert!(
9827 parse_ref_advertisements(
9828 ObjectFormat::Sha1,
9829 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
9830 )
9831 .is_err()
9832 );
9833 assert!(parse_ref_advertisements(
9834 ObjectFormat::Sha1,
9835 &[
9836 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9837 PktLineFrame::Data(
9838 b"2222222222222222222222222222222222222222 refs/heads/main\0thin-pack\n"
9839 .to_vec(),
9840 ),
9841 PktLineFrame::Flush,
9842 ],
9843 )
9844 .is_err());
9845 assert!(parse_ref_advertisement_set(
9846 ObjectFormat::Sha1,
9847 &[
9848 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9849 PktLineFrame::Data(b"version 1\n".to_vec()),
9850 PktLineFrame::Flush,
9851 ],
9852 )
9853 .is_err());
9854 assert!(
9855 parse_ref_advertisement_set(
9856 ObjectFormat::Sha1,
9857 &[
9858 PktLineFrame::Data(b"version 2\n".to_vec()),
9859 PktLineFrame::Flush,
9860 ],
9861 )
9862 .is_err()
9863 );
9864 assert!(
9865 parse_ref_advertisement_set(
9866 ObjectFormat::Sha1,
9867 &[
9868 PktLineFrame::Data(
9869 b"shallow 1111111111111111111111111111111111111111\n".to_vec()
9870 ),
9871 PktLineFrame::Flush,
9872 ],
9873 )
9874 .is_err()
9875 );
9876 assert!(parse_ref_advertisement_set(
9877 ObjectFormat::Sha1,
9878 &[
9879 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9880 PktLineFrame::Data(b"shallow not-an-oid\n".to_vec()),
9881 PktLineFrame::Flush,
9882 ],
9883 )
9884 .is_err());
9885 assert!(parse_ref_advertisement_set(
9886 ObjectFormat::Sha1,
9887 &[
9888 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9889 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
9890 PktLineFrame::Data(
9891 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
9892 ),
9893 PktLineFrame::Flush,
9894 ],
9895 )
9896 .is_err());
9897 assert!(
9898 encode_ref_advertisements(&[
9899 RefAdvertisement {
9900 oid: ObjectId::from_hex(
9901 ObjectFormat::Sha1,
9902 "1111111111111111111111111111111111111111",
9903 )
9904 .expect("test operation should succeed"),
9905 name: "HEAD".into(),
9906 capabilities: Vec::new(),
9907 },
9908 RefAdvertisement {
9909 oid: ObjectId::from_hex(
9910 ObjectFormat::Sha1,
9911 "2222222222222222222222222222222222222222",
9912 )
9913 .expect("test operation should succeed"),
9914 name: "refs/heads/main".into(),
9915 capabilities: vec![Capability {
9916 name: "thin-pack".into(),
9917 value: None,
9918 }],
9919 },
9920 ])
9921 .is_err()
9922 );
9923 assert!(
9924 encode_ref_advertisement(&RefAdvertisement {
9925 oid: ObjectId::from_hex(
9926 ObjectFormat::Sha1,
9927 "1111111111111111111111111111111111111111",
9928 )
9929 .expect("test operation should succeed"),
9930 name: "bad ref".into(),
9931 capabilities: Vec::new(),
9932 })
9933 .is_err()
9934 );
9935 assert!(
9936 encode_ref_advertisement_set(&RefAdvertisementSet {
9937 protocol: ProtocolVersion::V2,
9938 refs: Vec::new(),
9939 shallow: Vec::new(),
9940 })
9941 .is_err()
9942 );
9943 assert!(
9944 encode_ref_advertisement_set(&RefAdvertisementSet {
9945 protocol: ProtocolVersion::V0,
9946 refs: Vec::new(),
9947 shallow: vec![
9948 ObjectId::from_hex(
9949 ObjectFormat::Sha1,
9950 "1111111111111111111111111111111111111111",
9951 )
9952 .expect("test operation should succeed")
9953 ],
9954 })
9955 .is_err()
9956 );
9957 }
9958
9959 #[test]
9960 fn dumb_http_info_refs_parse_and_encode_records() {
9961 let main = ObjectId::from_hex(
9962 ObjectFormat::Sha1,
9963 "1111111111111111111111111111111111111111",
9964 )
9965 .expect("test operation should succeed");
9966 let tag = ObjectId::from_hex(
9967 ObjectFormat::Sha1,
9968 "2222222222222222222222222222222222222222",
9969 )
9970 .expect("test operation should succeed");
9971 let peeled = ObjectId::from_hex(
9972 ObjectFormat::Sha1,
9973 "3333333333333333333333333333333333333333",
9974 )
9975 .expect("test operation should succeed");
9976 let input = b"1111111111111111111111111111111111111111\trefs/heads/main\n2222222222222222222222222222222222222222\trefs/tags/v1.0\n3333333333333333333333333333333333333333\trefs/tags/v1.0^{}\n";
9977
9978 let records = parse_dumb_http_info_refs(ObjectFormat::Sha1, input)
9979 .expect("test operation should succeed");
9980 assert_eq!(
9981 records,
9982 vec![
9983 DumbHttpRefRecord {
9984 oid: main,
9985 name: "refs/heads/main".into(),
9986 peeled: false,
9987 },
9988 DumbHttpRefRecord {
9989 oid: tag,
9990 name: "refs/tags/v1.0".into(),
9991 peeled: false,
9992 },
9993 DumbHttpRefRecord {
9994 oid: peeled,
9995 name: "refs/tags/v1.0".into(),
9996 peeled: true,
9997 },
9998 ]
9999 );
10000 assert_eq!(
10001 encode_dumb_http_info_refs(&records).expect("test operation should succeed"),
10002 input
10003 );
10004 assert_eq!(
10005 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"")
10006 .expect("test operation should succeed"),
10007 Vec::<DumbHttpRefRecord>::new()
10008 );
10009 }
10010
10011 #[test]
10012 fn dumb_http_info_refs_streams_round_trip() {
10013 let records = vec![DumbHttpRefRecord {
10014 oid: ObjectId::from_hex(
10015 ObjectFormat::Sha1,
10016 "1111111111111111111111111111111111111111",
10017 )
10018 .expect("test operation should succeed"),
10019 name: "refs/heads/main".into(),
10020 peeled: false,
10021 }];
10022 let mut encoded = Vec::new();
10023 write_dumb_http_info_refs(&mut encoded, &records).expect("test operation should succeed");
10024 let mut input = encoded.as_slice();
10025 assert_eq!(
10026 read_dumb_http_info_refs(ObjectFormat::Sha1, &mut input)
10027 .expect("test operation should succeed"),
10028 records
10029 );
10030 assert!(input.is_empty());
10031 }
10032
10033 #[test]
10034 fn dumb_http_info_refs_reject_malformed_records() {
10035 assert!(
10036 parse_dumb_http_info_refs(
10037 ObjectFormat::Sha1,
10038 b"1111111111111111111111111111111111111111 refs/heads/main\n",
10039 )
10040 .is_err()
10041 );
10042 assert!(
10043 parse_dumb_http_info_refs(
10044 ObjectFormat::Sha1,
10045 b"1111111111111111111111111111111111111111\trefs/heads/main",
10046 )
10047 .is_err()
10048 );
10049 assert!(
10050 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"not-an-oid\trefs/heads/main\n")
10051 .is_err()
10052 );
10053 assert!(
10054 parse_dumb_http_info_refs(
10055 ObjectFormat::Sha1,
10056 b"1111111111111111111111111111111111111111\tbad ref\n",
10057 )
10058 .is_err()
10059 );
10060 assert!(
10061 encode_dumb_http_info_refs(&[DumbHttpRefRecord {
10062 oid: ObjectId::from_hex(
10063 ObjectFormat::Sha1,
10064 "1111111111111111111111111111111111111111",
10065 )
10066 .expect("test operation should succeed"),
10067 name: "refs/tags/v1.0^{}".into(),
10068 peeled: false,
10069 }])
10070 .is_err()
10071 );
10072 }
10073
10074 #[test]
10075 fn dumb_http_alternates_parse_and_encode_locations() {
10076 let input = b"https://example.com/base.git/objects/\n../other.git/objects/\n";
10077 let alternates = parse_dumb_http_alternates(input).expect("test operation should succeed");
10078 assert_eq!(
10079 alternates,
10080 vec![
10081 "https://example.com/base.git/objects/".to_string(),
10082 "../other.git/objects/".to_string(),
10083 ]
10084 );
10085 assert_eq!(
10086 encode_dumb_http_alternates(&alternates).expect("test operation should succeed"),
10087 input
10088 );
10089 assert_eq!(
10090 parse_dumb_http_alternates(b"").expect("test operation should succeed"),
10091 Vec::<String>::new()
10092 );
10093 }
10094
10095 #[test]
10096 fn dumb_http_alternates_streams_round_trip() {
10097 let alternates = vec!["https://example.com/base.git/objects/".to_string()];
10098 let mut encoded = Vec::new();
10099 write_dumb_http_alternates(&mut encoded, &alternates)
10100 .expect("test operation should succeed");
10101 let mut input = encoded.as_slice();
10102 assert_eq!(
10103 read_dumb_http_alternates(&mut input).expect("test operation should succeed"),
10104 alternates
10105 );
10106 assert!(input.is_empty());
10107 }
10108
10109 #[test]
10110 fn dumb_http_alternates_reject_malformed_lines() {
10111 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/").is_err());
10112 assert!(parse_dumb_http_alternates(b"\n").is_err());
10113 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/\r\n").is_err());
10114 assert!(encode_dumb_http_alternates(&["bad\nalternate".to_string()]).is_err());
10115 }
10116
10117 #[test]
10118 fn dumb_http_packs_parse_and_encode_pack_records() {
10119 let first = ObjectId::from_hex(
10120 ObjectFormat::Sha1,
10121 "1111111111111111111111111111111111111111",
10122 )
10123 .expect("test operation should succeed");
10124 let second = ObjectId::from_hex(
10125 ObjectFormat::Sha1,
10126 "2222222222222222222222222222222222222222",
10127 )
10128 .expect("test operation should succeed");
10129 let input = b"P pack-1111111111111111111111111111111111111111.pack\nP pack-2222222222222222222222222222222222222222.pack\n";
10130 let records = parse_dumb_http_packs(ObjectFormat::Sha1, input)
10131 .expect("test operation should succeed");
10132 assert_eq!(
10133 records,
10134 vec![
10135 DumbHttpPackRecord { hash: first },
10136 DumbHttpPackRecord { hash: second },
10137 ]
10138 );
10139 assert_eq!(
10140 encode_dumb_http_packs(&records).expect("test operation should succeed"),
10141 input
10142 );
10143 assert_eq!(
10144 parse_dumb_http_packs(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
10145 Vec::<DumbHttpPackRecord>::new()
10146 );
10147 }
10148
10149 #[test]
10150 fn dumb_http_packs_streams_round_trip() {
10151 let records = vec![DumbHttpPackRecord {
10152 hash: ObjectId::from_hex(
10153 ObjectFormat::Sha1,
10154 "1111111111111111111111111111111111111111",
10155 )
10156 .expect("test operation should succeed"),
10157 }];
10158 let mut encoded = Vec::new();
10159 write_dumb_http_packs(&mut encoded, &records).expect("test operation should succeed");
10160 let mut input = encoded.as_slice();
10161 assert_eq!(
10162 read_dumb_http_packs(ObjectFormat::Sha1, &mut input)
10163 .expect("test operation should succeed"),
10164 records
10165 );
10166 assert!(input.is_empty());
10167 }
10168
10169 #[test]
10170 fn dumb_http_packs_reject_malformed_records() {
10171 assert!(
10172 parse_dumb_http_packs(
10173 ObjectFormat::Sha1,
10174 b"P pack-1111111111111111111111111111111111111111.pack",
10175 )
10176 .is_err()
10177 );
10178 assert!(
10179 parse_dumb_http_packs(
10180 ObjectFormat::Sha1,
10181 b"pack-1111111111111111111111111111111111111111.pack\n",
10182 )
10183 .is_err()
10184 );
10185 assert!(parse_dumb_http_packs(ObjectFormat::Sha1, b"P pack-not-a-hash.pack\n",).is_err());
10186 assert!(
10187 parse_dumb_http_packs(
10188 ObjectFormat::Sha1,
10189 b"P pack-1111111111111111111111111111111111111111.idx\n",
10190 )
10191 .is_err()
10192 );
10193 }
10194
10195 #[test]
10196 fn upload_pack_features_parse_encode_and_validate_request() {
10197 let capabilities = vec![
10198 Capability {
10199 name: "multi_ack".into(),
10200 value: None,
10201 },
10202 Capability {
10203 name: "multi_ack_detailed".into(),
10204 value: None,
10205 },
10206 Capability {
10207 name: "no-done".into(),
10208 value: None,
10209 },
10210 Capability {
10211 name: "thin-pack".into(),
10212 value: None,
10213 },
10214 Capability {
10215 name: "side-band-64k".into(),
10216 value: None,
10217 },
10218 Capability {
10219 name: "ofs-delta".into(),
10220 value: None,
10221 },
10222 Capability {
10223 name: "shallow".into(),
10224 value: None,
10225 },
10226 Capability {
10227 name: "deepen-since".into(),
10228 value: None,
10229 },
10230 Capability {
10231 name: "deepen-not".into(),
10232 value: None,
10233 },
10234 Capability {
10235 name: "include-tag".into(),
10236 value: None,
10237 },
10238 Capability {
10239 name: "no-progress".into(),
10240 value: None,
10241 },
10242 Capability {
10243 name: "filter".into(),
10244 value: None,
10245 },
10246 Capability {
10247 name: "agent".into(),
10248 value: Some("git/2.54.0".into()),
10249 },
10250 Capability {
10251 name: "object-format".into(),
10252 value: Some("sha256".into()),
10253 },
10254 Capability {
10255 name: "symref".into(),
10256 value: Some("HEAD:refs/heads/main".into()),
10257 },
10258 Capability {
10259 name: "custom".into(),
10260 value: Some("value".into()),
10261 },
10262 ];
10263 let features =
10264 parse_upload_pack_features(&capabilities).expect("test operation should succeed");
10265 assert_eq!(
10266 features,
10267 UploadPackFeatures {
10268 multi_ack: true,
10269 multi_ack_detailed: true,
10270 no_done: true,
10271 thin_pack: true,
10272 side_band_64k: true,
10273 ofs_delta: true,
10274 shallow: true,
10275 deepen_since: true,
10276 deepen_not: true,
10277 include_tag: true,
10278 no_progress: true,
10279 filter: true,
10280 agent: Some("git/2.54.0".into()),
10281 object_format: Some(ObjectFormat::Sha256),
10282 symrefs: vec!["HEAD:refs/heads/main".into()],
10283 unknown: vec![Capability {
10284 name: "custom".into(),
10285 value: Some("value".into()),
10286 }],
10287 ..UploadPackFeatures::default()
10288 }
10289 );
10290 assert_eq!(
10291 encode_upload_pack_features(&features).expect("test operation should succeed"),
10292 capabilities
10293 );
10294
10295 let request = UploadPackRequest {
10296 wants: vec![
10297 ObjectId::from_hex(
10298 ObjectFormat::Sha1,
10299 "1111111111111111111111111111111111111111",
10300 )
10301 .expect("test operation should succeed"),
10302 ],
10303 capabilities: vec![
10304 Capability {
10305 name: "multi_ack_detailed".into(),
10306 value: None,
10307 },
10308 Capability {
10309 name: "thin-pack".into(),
10310 value: None,
10311 },
10312 Capability {
10313 name: "side-band-64k".into(),
10314 value: None,
10315 },
10316 Capability {
10317 name: "ofs-delta".into(),
10318 value: None,
10319 },
10320 Capability {
10321 name: "include-tag".into(),
10322 value: None,
10323 },
10324 Capability {
10325 name: "agent".into(),
10326 value: Some("sley".into()),
10327 },
10328 ],
10329 shallow: vec![
10330 ObjectId::from_hex(
10331 ObjectFormat::Sha1,
10332 "2222222222222222222222222222222222222222",
10333 )
10334 .expect("test operation should succeed"),
10335 ],
10336 deepen: Some(5),
10337 deepen_since: Some(1_710_000_000),
10338 deepen_not: vec!["refs/tags/base".into()],
10339 filter: Some("blob:none".into()),
10340 };
10341 validate_upload_pack_request_features(&features, &request)
10342 .expect("test operation should succeed");
10343 }
10344
10345 #[test]
10346 fn upload_pack_features_reject_invalid_requests() {
10347 let want = ObjectId::from_hex(
10348 ObjectFormat::Sha1,
10349 "1111111111111111111111111111111111111111",
10350 )
10351 .expect("test operation should succeed");
10352 let features = UploadPackFeatures {
10353 thin_pack: true,
10354 side_band: true,
10355 ..UploadPackFeatures::default()
10356 };
10357
10358 assert!(
10359 validate_upload_pack_request_features(
10360 &features,
10361 &UploadPackRequest {
10362 wants: vec![want],
10363 capabilities: vec![Capability {
10364 name: "ofs-delta".into(),
10365 value: None,
10366 }],
10367 ..UploadPackRequest::default()
10368 },
10369 )
10370 .is_err()
10371 );
10372 assert!(
10373 validate_upload_pack_request_features(
10374 &features,
10375 &UploadPackRequest {
10376 wants: vec![want],
10377 shallow: vec![want],
10378 ..UploadPackRequest::default()
10379 },
10380 )
10381 .is_err()
10382 );
10383 assert!(
10384 validate_upload_pack_request_features(
10385 &features,
10386 &UploadPackRequest {
10387 wants: vec![want],
10388 filter: Some("blob:none".into()),
10389 ..UploadPackRequest::default()
10390 },
10391 )
10392 .is_err()
10393 );
10394 assert!(
10395 validate_upload_pack_request_features(
10396 &UploadPackFeatures {
10397 side_band: true,
10398 side_band_64k: true,
10399 ..UploadPackFeatures::default()
10400 },
10401 &UploadPackRequest {
10402 wants: vec![want],
10403 capabilities: vec![
10404 Capability {
10405 name: "side-band".into(),
10406 value: None,
10407 },
10408 Capability {
10409 name: "side-band-64k".into(),
10410 value: None,
10411 },
10412 ],
10413 ..UploadPackRequest::default()
10414 },
10415 )
10416 .is_err()
10417 );
10418
10419 assert!(
10420 parse_upload_pack_features(&[
10421 Capability {
10422 name: "thin-pack".into(),
10423 value: None,
10424 },
10425 Capability {
10426 name: "thin-pack".into(),
10427 value: None,
10428 },
10429 ])
10430 .is_err()
10431 );
10432 assert!(
10433 encode_upload_pack_features(&UploadPackFeatures {
10434 unknown: vec![Capability {
10435 name: "filter".into(),
10436 value: None,
10437 }],
10438 ..UploadPackFeatures::default()
10439 })
10440 .is_err()
10441 );
10442 }
10443
10444 #[test]
10445 fn upload_pack_raw_response_builder_filters_unknown_haves_and_builds_pack() {
10446 let want = ObjectId::from_hex(
10447 ObjectFormat::Sha1,
10448 "1111111111111111111111111111111111111111",
10449 )
10450 .expect("test operation should succeed");
10451 let known_have = ObjectId::from_hex(
10452 ObjectFormat::Sha1,
10453 "2222222222222222222222222222222222222222",
10454 )
10455 .expect("test operation should succeed");
10456 let unknown_have = ObjectId::from_hex(
10457 ObjectFormat::Sha1,
10458 "3333333333333333333333333333333333333333",
10459 )
10460 .expect("test operation should succeed");
10461 let existing = std::collections::HashSet::from([want, known_have]);
10462
10463 let response = build_upload_pack_raw_packfile_response(
10464 &UploadPackFeatures::default(),
10465 UploadPackRequest {
10466 wants: vec![want],
10467 ..UploadPackRequest::default()
10468 },
10469 [known_have, unknown_have],
10470 |oid| Ok(existing.contains(oid)),
10471 |wants, haves| {
10472 assert_eq!(wants, vec![want]);
10473 assert_eq!(haves, vec![known_have]);
10474 Ok(Some(b"PACKmock".to_vec()))
10475 },
10476 )
10477 .expect("test operation should succeed");
10478
10479 assert_eq!(
10480 response.acknowledgments,
10481 vec![UploadPackAcknowledgment::Nak]
10482 );
10483 assert_eq!(response.packfile, b"PACKmock");
10484 }
10485
10486 #[test]
10487 fn upload_pack_raw_response_builder_rejects_missing_want_and_empty_pack() {
10488 let want = ObjectId::from_hex(
10489 ObjectFormat::Sha1,
10490 "1111111111111111111111111111111111111111",
10491 )
10492 .expect("test operation should succeed");
10493
10494 assert!(
10495 build_upload_pack_raw_packfile_response(
10496 &UploadPackFeatures::default(),
10497 UploadPackRequest {
10498 wants: vec![want],
10499 ..UploadPackRequest::default()
10500 },
10501 Vec::<ObjectId>::new(),
10502 |_| Ok(false),
10503 |_, _| Ok(Some(b"PACKmock".to_vec())),
10504 )
10505 .is_err()
10506 );
10507
10508 assert!(
10509 build_upload_pack_raw_packfile_response(
10510 &UploadPackFeatures::default(),
10511 UploadPackRequest {
10512 wants: vec![want],
10513 ..UploadPackRequest::default()
10514 },
10515 Vec::<ObjectId>::new(),
10516 |_| Ok(true),
10517 |_, _| Ok(None),
10518 )
10519 .is_err()
10520 );
10521 }
10522
10523 #[test]
10524 fn upload_pack_request_parses_and_encodes_initial_fetch_request() {
10525 let want = ObjectId::from_hex(
10526 ObjectFormat::Sha1,
10527 "1111111111111111111111111111111111111111",
10528 )
10529 .expect("test operation should succeed");
10530 let second_want = ObjectId::from_hex(
10531 ObjectFormat::Sha1,
10532 "2222222222222222222222222222222222222222",
10533 )
10534 .expect("test operation should succeed");
10535 let shallow = ObjectId::from_hex(
10536 ObjectFormat::Sha1,
10537 "3333333333333333333333333333333333333333",
10538 )
10539 .expect("test operation should succeed");
10540 let frames = vec![
10541 PktLineFrame::Data(
10542 b"want 1111111111111111111111111111111111111111 multi_ack thin-pack agent=git/2.54.0\n"
10543 .to_vec(),
10544 ),
10545 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10546 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
10547 PktLineFrame::Data(b"deepen-since 1710000000\n".to_vec()),
10548 PktLineFrame::Data(b"deepen-not refs/tags/base\n".to_vec()),
10549 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10550 PktLineFrame::Flush,
10551 ];
10552 let request = parse_upload_pack_request(ObjectFormat::Sha1, &frames)
10553 .expect("test operation should succeed")
10554 .expect("test operation should succeed");
10555 assert_eq!(
10556 request,
10557 UploadPackRequest {
10558 wants: vec![want, second_want],
10559 capabilities: vec![
10560 Capability {
10561 name: "multi_ack".into(),
10562 value: None,
10563 },
10564 Capability {
10565 name: "thin-pack".into(),
10566 value: None,
10567 },
10568 Capability {
10569 name: "agent".into(),
10570 value: Some("git/2.54.0".into()),
10571 },
10572 ],
10573 shallow: vec![shallow],
10574 deepen: None,
10575 deepen_since: Some(1_710_000_000),
10576 deepen_not: vec!["refs/tags/base".into()],
10577 filter: Some("blob:none".into()),
10578 }
10579 );
10580 assert_eq!(
10581 encode_upload_pack_request(Some(&request)).expect("test operation should succeed"),
10582 frames
10583 );
10584 assert_eq!(
10585 parse_upload_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10586 .expect("test operation should succeed"),
10587 None
10588 );
10589 assert_eq!(
10590 encode_upload_pack_request(None).expect("test operation should succeed"),
10591 vec![PktLineFrame::Flush]
10592 );
10593 }
10594
10595 #[test]
10596 fn upload_pack_request_streams_round_trip() {
10597 let request = UploadPackRequest {
10598 wants: vec![
10599 ObjectId::from_hex(
10600 ObjectFormat::Sha1,
10601 "1111111111111111111111111111111111111111",
10602 )
10603 .expect("test operation should succeed"),
10604 ],
10605 capabilities: vec![Capability {
10606 name: "ofs-delta".into(),
10607 value: None,
10608 }],
10609 deepen: Some(10),
10610 ..UploadPackRequest::default()
10611 };
10612 let mut encoded = Vec::new();
10613 write_upload_pack_request(&mut encoded, Some(&request))
10614 .expect("test operation should succeed");
10615 encoded.extend_from_slice(b"tail");
10616
10617 let mut input = encoded.as_slice();
10618 assert_eq!(
10619 read_upload_pack_request(ObjectFormat::Sha1, &mut input)
10620 .expect("test operation should succeed"),
10621 Some(request)
10622 );
10623 assert_eq!(input, b"tail");
10624 }
10625
10626 #[test]
10627 fn upload_pack_request_rejects_malformed_requests() {
10628 assert!(
10629 parse_upload_pack_request(
10630 ObjectFormat::Sha1,
10631 &[PktLineFrame::Data(
10632 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10633 )],
10634 )
10635 .is_err()
10636 );
10637 assert!(
10638 parse_upload_pack_request(
10639 ObjectFormat::Sha1,
10640 &[
10641 PktLineFrame::Data(
10642 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10643 ),
10644 PktLineFrame::Flush,
10645 ],
10646 )
10647 .is_err()
10648 );
10649 assert!(
10650 parse_upload_pack_request(
10651 ObjectFormat::Sha1,
10652 &[
10653 PktLineFrame::Data(
10654 b"want 1111111111111111111111111111111111111111 thin-pack\n".to_vec(),
10655 ),
10656 PktLineFrame::Data(
10657 b"want 2222222222222222222222222222222222222222 ofs-delta\n".to_vec(),
10658 ),
10659 PktLineFrame::Flush,
10660 ],
10661 )
10662 .is_err()
10663 );
10664 assert!(parse_upload_pack_request(
10665 ObjectFormat::Sha1,
10666 &[
10667 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10668 PktLineFrame::Data(b"deepen 1\n".to_vec()),
10669 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10670 PktLineFrame::Flush,
10671 ],
10672 )
10673 .is_err());
10674 assert!(parse_upload_pack_request(
10675 ObjectFormat::Sha1,
10676 &[
10677 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10678 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10679 PktLineFrame::Data(b"filter tree:0\n".to_vec()),
10680 PktLineFrame::Flush,
10681 ],
10682 )
10683 .is_err());
10684 assert!(encode_upload_pack_request(Some(&UploadPackRequest::default())).is_err());
10685 assert!(
10686 encode_upload_pack_request(Some(&UploadPackRequest {
10687 wants: vec![
10688 ObjectId::from_hex(
10689 ObjectFormat::Sha1,
10690 "1111111111111111111111111111111111111111",
10691 )
10692 .expect("test operation should succeed")
10693 ],
10694 deepen: Some(0),
10695 ..UploadPackRequest::default()
10696 }))
10697 .is_err()
10698 );
10699 }
10700
10701 #[test]
10702 fn upload_pack_shallow_update_parses_and_encodes_records() {
10703 let shallow = ObjectId::from_hex(
10704 ObjectFormat::Sha1,
10705 "1111111111111111111111111111111111111111",
10706 )
10707 .expect("test operation should succeed");
10708 let unshallow = ObjectId::from_hex(
10709 ObjectFormat::Sha1,
10710 "2222222222222222222222222222222222222222",
10711 )
10712 .expect("test operation should succeed");
10713 let frames = vec![
10714 PktLineFrame::Data(b"shallow 1111111111111111111111111111111111111111\n".to_vec()),
10715 PktLineFrame::Data(b"unshallow 2222222222222222222222222222222222222222\n".to_vec()),
10716 PktLineFrame::Flush,
10717 ];
10718 let entries = parse_upload_pack_shallow_update(ObjectFormat::Sha1, &frames)
10719 .expect("test operation should succeed");
10720 assert_eq!(
10721 entries,
10722 vec![
10723 ProtocolV2FetchShallowInfo::Shallow(shallow),
10724 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
10725 ]
10726 );
10727 assert_eq!(
10728 encode_upload_pack_shallow_update(&entries).expect("test operation should succeed"),
10729 frames
10730 );
10731 assert_eq!(
10732 parse_upload_pack_shallow_update(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10733 .expect("test operation should succeed"),
10734 Vec::<ProtocolV2FetchShallowInfo>::new()
10735 );
10736 }
10737
10738 #[test]
10739 fn upload_pack_shallow_update_streams_round_trip() {
10740 let entries = vec![ProtocolV2FetchShallowInfo::Shallow(
10741 ObjectId::from_hex(
10742 ObjectFormat::Sha1,
10743 "1111111111111111111111111111111111111111",
10744 )
10745 .expect("test operation should succeed"),
10746 )];
10747 let mut encoded = Vec::new();
10748 write_upload_pack_shallow_update(&mut encoded, &entries)
10749 .expect("test operation should succeed");
10750 encoded.extend_from_slice(b"tail");
10751
10752 let mut input = encoded.as_slice();
10753 assert_eq!(
10754 read_upload_pack_shallow_update(ObjectFormat::Sha1, &mut input)
10755 .expect("test operation should succeed"),
10756 entries
10757 );
10758 assert_eq!(input, b"tail");
10759 }
10760
10761 #[test]
10762 fn upload_pack_shallow_update_rejects_malformed_records() {
10763 assert!(
10764 parse_upload_pack_shallow_update(
10765 ObjectFormat::Sha1,
10766 &[PktLineFrame::Data(
10767 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10768 )],
10769 )
10770 .is_err()
10771 );
10772 assert!(
10773 parse_upload_pack_shallow_update(
10774 ObjectFormat::Sha1,
10775 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10776 )
10777 .is_err()
10778 );
10779 assert!(
10780 parse_upload_pack_shallow_update(
10781 ObjectFormat::Sha1,
10782 &[
10783 PktLineFrame::Data(
10784 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10785 ),
10786 PktLineFrame::Flush,
10787 PktLineFrame::Data(
10788 b"unshallow 2222222222222222222222222222222222222222\n".to_vec(),
10789 ),
10790 ],
10791 )
10792 .is_err()
10793 );
10794 assert!(
10795 parse_upload_pack_shallow_update(
10796 ObjectFormat::Sha1,
10797 &[
10798 PktLineFrame::Data(
10799 b"unsupported 1111111111111111111111111111111111111111\n".to_vec(),
10800 ),
10801 PktLineFrame::Flush,
10802 ],
10803 )
10804 .is_err()
10805 );
10806 }
10807
10808 #[test]
10809 fn upload_pack_negotiation_request_parses_flush_and_done_rounds() {
10810 let have = ObjectId::from_hex(
10811 ObjectFormat::Sha1,
10812 "1111111111111111111111111111111111111111",
10813 )
10814 .expect("test operation should succeed");
10815 let second_have = ObjectId::from_hex(
10816 ObjectFormat::Sha1,
10817 "2222222222222222222222222222222222222222",
10818 )
10819 .expect("test operation should succeed");
10820 let flush_round = vec![
10821 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10822 PktLineFrame::Data(b"have 2222222222222222222222222222222222222222\n".to_vec()),
10823 PktLineFrame::Flush,
10824 ];
10825 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &flush_round)
10826 .expect("test operation should succeed");
10827 assert_eq!(
10828 request,
10829 UploadPackNegotiationRequest {
10830 haves: vec![have, second_have],
10831 done: false,
10832 }
10833 );
10834 assert_eq!(
10835 encode_upload_pack_negotiation_request(&request)
10836 .expect("test operation should succeed"),
10837 flush_round
10838 );
10839
10840 let done_round = vec![
10841 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10842 PktLineFrame::Data(b"done\n".to_vec()),
10843 ];
10844 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &done_round)
10845 .expect("test operation should succeed");
10846 assert_eq!(
10847 request,
10848 UploadPackNegotiationRequest {
10849 haves: vec![have],
10850 done: true,
10851 }
10852 );
10853 assert_eq!(
10854 encode_upload_pack_negotiation_request(&request)
10855 .expect("test operation should succeed"),
10856 done_round
10857 );
10858 }
10859
10860 #[test]
10861 fn upload_pack_negotiation_request_streams_round_trip() {
10862 let first = UploadPackNegotiationRequest {
10863 haves: vec![
10864 ObjectId::from_hex(
10865 ObjectFormat::Sha1,
10866 "1111111111111111111111111111111111111111",
10867 )
10868 .expect("test operation should succeed"),
10869 ],
10870 done: false,
10871 };
10872 let second = UploadPackNegotiationRequest {
10873 haves: Vec::new(),
10874 done: true,
10875 };
10876 let mut encoded = Vec::new();
10877 write_upload_pack_negotiation_request(&mut encoded, &first)
10878 .expect("test operation should succeed");
10879 write_upload_pack_negotiation_request(&mut encoded, &second)
10880 .expect("test operation should succeed");
10881 encoded.extend_from_slice(b"tail");
10882
10883 let mut input = encoded.as_slice();
10884 assert_eq!(
10885 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10886 .expect("test operation should succeed"),
10887 first
10888 );
10889 assert_eq!(
10890 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10891 .expect("test operation should succeed"),
10892 second
10893 );
10894 assert_eq!(input, b"tail");
10895 }
10896
10897 #[test]
10898 fn upload_pack_negotiation_request_rejects_malformed_rounds() {
10899 assert!(
10900 parse_upload_pack_negotiation_request(
10901 ObjectFormat::Sha1,
10902 &[PktLineFrame::Data(
10903 b"have 1111111111111111111111111111111111111111\n".to_vec(),
10904 )],
10905 )
10906 .is_err()
10907 );
10908 assert!(
10909 parse_upload_pack_negotiation_request(
10910 ObjectFormat::Sha1,
10911 &[PktLineFrame::Data(
10912 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10913 )],
10914 )
10915 .is_err()
10916 );
10917 assert!(parse_upload_pack_negotiation_request(
10918 ObjectFormat::Sha1,
10919 &[
10920 PktLineFrame::Data(b"done\n".to_vec()),
10921 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec(),),
10922 ],
10923 )
10924 .is_err());
10925 assert!(
10926 parse_upload_pack_negotiation_request(
10927 ObjectFormat::Sha1,
10928 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10929 )
10930 .is_err()
10931 );
10932 }
10933
10934 #[test]
10935 fn upload_pack_acknowledgments_parse_and_encode_statuses() {
10936 let oid = ObjectId::from_hex(
10937 ObjectFormat::Sha1,
10938 "1111111111111111111111111111111111111111",
10939 )
10940 .expect("test operation should succeed");
10941 assert_eq!(
10942 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"NAK\n")
10943 .expect("test operation should succeed"),
10944 UploadPackAcknowledgment::Nak
10945 );
10946 for (payload, status) in [
10947 (
10948 b"ACK 1111111111111111111111111111111111111111\n".as_slice(),
10949 None,
10950 ),
10951 (
10952 b"ACK 1111111111111111111111111111111111111111 continue\n".as_slice(),
10953 Some(UploadPackAckStatus::Continue),
10954 ),
10955 (
10956 b"ACK 1111111111111111111111111111111111111111 common\n".as_slice(),
10957 Some(UploadPackAckStatus::Common),
10958 ),
10959 (
10960 b"ACK 1111111111111111111111111111111111111111 ready\n".as_slice(),
10961 Some(UploadPackAckStatus::Ready),
10962 ),
10963 ] {
10964 let acknowledgment = parse_upload_pack_acknowledgment(ObjectFormat::Sha1, payload)
10965 .expect("test operation should succeed");
10966 assert_eq!(
10967 acknowledgment,
10968 UploadPackAcknowledgment::Ack { oid, status }
10969 );
10970 assert_eq!(
10971 encode_upload_pack_acknowledgment(&acknowledgment)
10972 .expect("test operation should succeed"),
10973 payload
10974 );
10975 }
10976 }
10977
10978 #[test]
10979 fn upload_pack_acknowledgments_stream_round_trip_and_reject_bad_lines() {
10980 let acknowledgment = UploadPackAcknowledgment::Ack {
10981 oid: ObjectId::from_hex(
10982 ObjectFormat::Sha1,
10983 "1111111111111111111111111111111111111111",
10984 )
10985 .expect("test operation should succeed"),
10986 status: Some(UploadPackAckStatus::Ready),
10987 };
10988 let mut encoded = Vec::new();
10989 write_upload_pack_acknowledgment(&mut encoded, &acknowledgment)
10990 .expect("test operation should succeed");
10991 encoded.extend_from_slice(b"tail");
10992
10993 let mut input = encoded.as_slice();
10994 assert_eq!(
10995 read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut input)
10996 .expect("test operation should succeed"),
10997 acknowledgment
10998 );
10999 assert_eq!(input, b"tail");
11000 assert!(parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ACK not-an-oid\n").is_err());
11001 assert!(
11002 parse_upload_pack_acknowledgment(
11003 ObjectFormat::Sha1,
11004 b"ACK 1111111111111111111111111111111111111111 unknown\n",
11005 )
11006 .is_err()
11007 );
11008 assert!(
11009 parse_upload_pack_acknowledgment(
11010 ObjectFormat::Sha1,
11011 b"ACK 1111111111111111111111111111111111111111 ready extra\n",
11012 )
11013 .is_err()
11014 );
11015 assert!(
11016 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ERR remote died\n").is_err()
11017 );
11018 assert!(read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut &b"0000"[..]).is_err());
11019 }
11020
11021 #[test]
11022 fn upload_pack_packfile_response_parses_acknowledgments_and_sideband() {
11023 let oid = ObjectId::from_hex(
11024 ObjectFormat::Sha1,
11025 "1111111111111111111111111111111111111111",
11026 )
11027 .expect("test operation should succeed");
11028 let frames = vec![
11029 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()),
11030 PktLineFrame::Data(b"NAK\n".to_vec()),
11031 PktLineFrame::Data(b"\x01PACK".to_vec()),
11032 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
11033 PktLineFrame::Data(b"\x01 bytes".to_vec()),
11034 PktLineFrame::Flush,
11035 ];
11036 let response = parse_upload_pack_packfile_response(ObjectFormat::Sha1, &frames)
11037 .expect("test operation should succeed");
11038 assert_eq!(
11039 response,
11040 UploadPackPackfileResponse {
11041 acknowledgments: vec![
11042 UploadPackAcknowledgment::Ack {
11043 oid,
11044 status: Some(UploadPackAckStatus::Common),
11045 },
11046 UploadPackAcknowledgment::Nak,
11047 ],
11048 sideband: vec![
11049 SideBandPacket {
11050 channel: SideBandChannel::Data,
11051 data: b"PACK".to_vec(),
11052 },
11053 SideBandPacket {
11054 channel: SideBandChannel::Progress,
11055 data: b"counting objects\n".to_vec(),
11056 },
11057 SideBandPacket {
11058 channel: SideBandChannel::Data,
11059 data: b" bytes".to_vec(),
11060 },
11061 ],
11062 }
11063 );
11064 assert_eq!(
11065 demux_upload_pack_packfile_response(&response).expect("test operation should succeed"),
11066 SideBandDemux {
11067 data: b"PACK bytes".to_vec(),
11068 progress: vec![b"counting objects\n".to_vec()],
11069 }
11070 );
11071 assert_eq!(
11072 encode_upload_pack_packfile_response(&response).expect("test operation should succeed"),
11073 frames
11074 );
11075 }
11076
11077 #[test]
11078 fn upload_pack_packfile_response_streams_round_trip() {
11079 let response = UploadPackPackfileResponse {
11080 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11081 sideband: vec![SideBandPacket {
11082 channel: SideBandChannel::Data,
11083 data: b"PACK".to_vec(),
11084 }],
11085 };
11086 let mut encoded = Vec::new();
11087 write_upload_pack_packfile_response(&mut encoded, &response)
11088 .expect("test operation should succeed");
11089 encoded.extend_from_slice(b"tail");
11090
11091 let mut input = encoded.as_slice();
11092 assert_eq!(
11093 read_upload_pack_packfile_response(ObjectFormat::Sha1, &mut input)
11094 .expect("test operation should succeed"),
11095 response
11096 );
11097 assert_eq!(input, b"tail");
11098 }
11099
11100 #[test]
11101 fn upload_pack_packfile_response_rejects_malformed_streams() {
11102 assert!(
11103 parse_upload_pack_packfile_response(
11104 ObjectFormat::Sha1,
11105 &[PktLineFrame::Data(b"NAK\n".to_vec())],
11106 )
11107 .is_err()
11108 );
11109 assert!(
11110 parse_upload_pack_packfile_response(
11111 ObjectFormat::Sha1,
11112 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
11113 )
11114 .is_err()
11115 );
11116 assert!(
11117 parse_upload_pack_packfile_response(
11118 ObjectFormat::Sha1,
11119 &[
11120 PktLineFrame::Data(b"\x01PACK".to_vec()),
11121 PktLineFrame::Data(
11122 b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()
11123 ),
11124 PktLineFrame::Flush,
11125 ],
11126 )
11127 .is_err()
11128 );
11129 assert!(
11130 parse_upload_pack_packfile_response(
11131 ObjectFormat::Sha1,
11132 &[
11133 PktLineFrame::Data(b"NAK\n".to_vec()),
11134 PktLineFrame::Flush,
11135 PktLineFrame::Data(b"\x01PACK".to_vec()),
11136 ],
11137 )
11138 .is_err()
11139 );
11140 assert!(
11141 parse_upload_pack_packfile_response(
11142 ObjectFormat::Sha1,
11143 &[
11144 PktLineFrame::Data(b"NAK\n".to_vec()),
11145 PktLineFrame::Data(b"\x04bad".to_vec()),
11146 PktLineFrame::Flush,
11147 ],
11148 )
11149 .is_err()
11150 );
11151 }
11152
11153 #[test]
11154 fn upload_pack_raw_packfile_response_parses_acknowledgments_and_raw_pack() {
11155 let oid = ObjectId::from_hex(
11156 ObjectFormat::Sha1,
11157 "1111111111111111111111111111111111111111",
11158 )
11159 .expect("test operation should succeed");
11160 let response = UploadPackRawPackfileResponse {
11161 acknowledgments: vec![
11162 UploadPackAcknowledgment::Ack {
11163 oid,
11164 status: Some(UploadPackAckStatus::Common),
11165 },
11166 UploadPackAcknowledgment::Nak,
11167 ],
11168 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11169 };
11170 let encoded = encode_upload_pack_raw_packfile_response(&response)
11171 .expect("test operation should succeed");
11172 assert_eq!(
11173 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &encoded)
11174 .expect("test operation should succeed"),
11175 response
11176 );
11177 }
11178
11179 #[test]
11180 fn upload_pack_raw_packfile_response_streams_round_trip() {
11181 let response = UploadPackRawPackfileResponse {
11182 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11183 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11184 };
11185 let mut encoded = Vec::new();
11186 write_upload_pack_raw_packfile_response(&mut encoded, &response)
11187 .expect("test operation should succeed");
11188 assert_eq!(
11189 encoded,
11190 encode_upload_pack_raw_packfile_response(&response)
11191 .expect("test operation should succeed")
11192 );
11193
11194 let mut input = encoded.as_slice();
11195 assert_eq!(
11196 read_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &mut input)
11197 .expect("test operation should succeed"),
11198 response
11199 );
11200 assert!(input.is_empty());
11201 }
11202
11203 #[test]
11204 fn upload_pack_raw_packfile_response_rejects_malformed_streams() {
11205 let ack = PktLineFrame::data(b"NAK\n".to_vec())
11206 .expect("test operation should succeed")
11207 .try_encode()
11208 .expect("test operation should succeed");
11209 let bad_ack = PktLineFrame::data(b"ACK not-an-oid\n".to_vec())
11210 .expect("test operation should succeed")
11211 .try_encode()
11212 .expect("test operation should succeed");
11213 let non_ack =
11214 PktLineFrame::data(b"have 1111111111111111111111111111111111111111\n".to_vec())
11215 .expect("test operation should succeed")
11216 .try_encode()
11217 .expect("test operation should succeed");
11218 let mut garbage_after_ack = ack.clone();
11219 garbage_after_ack.extend_from_slice(b"garbage");
11220
11221 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"").is_err());
11222 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &ack).is_err());
11223 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &bad_ack).is_err());
11224 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"0000PACK").is_err());
11225 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &non_ack).is_err());
11226 assert!(
11227 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &garbage_after_ack)
11228 .is_err()
11229 );
11230 assert!(
11231 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11232 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11233 packfile: Vec::new(),
11234 })
11235 .is_err()
11236 );
11237 assert!(
11238 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11239 acknowledgments: Vec::new(),
11240 packfile: b"not-a-pack".to_vec(),
11241 })
11242 .is_err()
11243 );
11244 }
11245
11246 #[test]
11247 fn upload_pack_request_encodes_deepen_request() {
11248 let want = ObjectId::from_hex(
11253 ObjectFormat::Sha1,
11254 "1111111111111111111111111111111111111111",
11255 )
11256 .expect("test operation should succeed");
11257 let boundary = ObjectId::from_hex(
11258 ObjectFormat::Sha1,
11259 "2222222222222222222222222222222222222222",
11260 )
11261 .expect("test operation should succeed");
11262 let request = UploadPackRequest {
11263 wants: vec![want],
11264 capabilities: vec![Capability {
11265 name: "shallow".into(),
11266 value: None,
11267 }],
11268 shallow: vec![boundary],
11269 deepen: Some(1),
11270 ..UploadPackRequest::default()
11271 };
11272 let mut encoded = Vec::new();
11273 write_upload_pack_request(&mut encoded, Some(&request))
11274 .expect("test operation should succeed");
11275 let mut expected = Vec::new();
11276 expected.extend_from_slice(b"003awant 1111111111111111111111111111111111111111 shallow\n");
11277 expected.extend_from_slice(b"0035shallow 2222222222222222222222222222222222222222\n");
11278 expected.extend_from_slice(b"000ddeepen 1\n");
11279 expected.extend_from_slice(b"0000");
11280 assert_eq!(encoded, expected);
11281 }
11282
11283 #[test]
11284 fn upload_pack_shallow_info_response_parses_shallow_unshallow_and_pack() {
11285 let shallow = ObjectId::from_hex(
11289 ObjectFormat::Sha1,
11290 "1111111111111111111111111111111111111111",
11291 )
11292 .expect("test operation should succeed");
11293 let unshallow = ObjectId::from_hex(
11294 ObjectFormat::Sha1,
11295 "2222222222222222222222222222222222222222",
11296 )
11297 .expect("test operation should succeed");
11298 let mut input = Vec::new();
11299 input.extend_from_slice(b"0035shallow 1111111111111111111111111111111111111111\n");
11300 input.extend_from_slice(b"0037unshallow 2222222222222222222222222222222222222222\n");
11301 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11303 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11304
11305 let (entries, response) =
11306 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11307 .expect("test operation should succeed");
11308 assert_eq!(
11309 entries,
11310 vec![
11311 ProtocolV2FetchShallowInfo::Shallow(shallow),
11312 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
11313 ]
11314 );
11315 assert_eq!(
11316 response,
11317 UploadPackRawPackfileResponse {
11318 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11319 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11320 }
11321 );
11322
11323 let mut stream = input.as_slice();
11325 let (read_entries, read_response) =
11326 read_upload_pack_shallow_info_and_raw_packfile_response(
11327 ObjectFormat::Sha1,
11328 &mut stream,
11329 )
11330 .expect("test operation should succeed");
11331 assert_eq!(read_entries, entries);
11332 assert_eq!(read_response, response);
11333 }
11334
11335 #[test]
11336 fn upload_pack_shallow_info_response_handles_empty_shallow_section() {
11337 let mut input = Vec::new();
11340 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11342 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11343
11344 let (entries, response) =
11345 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11346 .expect("test operation should succeed");
11347 assert!(entries.is_empty());
11348 assert_eq!(
11349 response.acknowledgments,
11350 vec![UploadPackAcknowledgment::Nak]
11351 );
11352 assert!(response.packfile.starts_with(b"PACK"));
11353 }
11354
11355 #[test]
11356 fn upload_pack_shallow_info_response_rejects_malformed_sections() {
11357 let truncated = b"0035shallow 1111111111111111111111111111111111111111\n".to_vec();
11359 assert!(
11360 parse_upload_pack_shallow_info_and_raw_packfile_response(
11361 ObjectFormat::Sha1,
11362 &truncated
11363 )
11364 .is_err()
11365 );
11366 let mut delimiter_section = Vec::new();
11368 delimiter_section.extend_from_slice(b"0001"); assert!(
11370 parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &delimiter_section).is_err()
11371 );
11372 let mut bad_line = Vec::new();
11374 bad_line.extend_from_slice(b"0008NAK\n");
11375 assert!(parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &bad_line).is_err());
11376 let mut no_pack = Vec::new();
11378 no_pack.extend_from_slice(b"0000"); no_pack.extend_from_slice(b"0008NAK\n");
11380 assert!(
11381 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &no_pack)
11382 .is_err()
11383 );
11384 }
11385
11386 #[test]
11387 fn receive_pack_request_parses_and_encodes_commands() {
11388 let old_id = ObjectId::from_hex(
11389 ObjectFormat::Sha1,
11390 "1111111111111111111111111111111111111111",
11391 )
11392 .expect("test operation should succeed");
11393 let new_id = ObjectId::from_hex(
11394 ObjectFormat::Sha1,
11395 "2222222222222222222222222222222222222222",
11396 )
11397 .expect("test operation should succeed");
11398 let delete_old_id = ObjectId::from_hex(
11399 ObjectFormat::Sha1,
11400 "3333333333333333333333333333333333333333",
11401 )
11402 .expect("test operation should succeed");
11403 let zero = ObjectId::from_hex(
11404 ObjectFormat::Sha1,
11405 "0000000000000000000000000000000000000000",
11406 )
11407 .expect("test operation should succeed");
11408 let shallow = ObjectId::from_hex(
11409 ObjectFormat::Sha1,
11410 "4444444444444444444444444444444444444444",
11411 )
11412 .expect("test operation should succeed");
11413 let frames = vec![
11414 PktLineFrame::Data(b"shallow 4444444444444444444444444444444444444444\n".to_vec()),
11415 PktLineFrame::Data(
11416 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status side-band-64k agent=git/2.54.0\n"
11417 .to_vec(),
11418 ),
11419 PktLineFrame::Data(
11420 b"3333333333333333333333333333333333333333 0000000000000000000000000000000000000000 refs/heads/old\n"
11421 .to_vec(),
11422 ),
11423 PktLineFrame::Flush,
11424 ];
11425 let request = parse_receive_pack_request(ObjectFormat::Sha1, &frames)
11426 .expect("test operation should succeed");
11427 assert_eq!(
11428 request,
11429 ReceivePackRequest {
11430 shallow: vec![shallow],
11431 commands: vec![
11432 ReceivePackCommand {
11433 old_id,
11434 new_id,
11435 name: "refs/heads/main".into(),
11436 },
11437 ReceivePackCommand {
11438 old_id: delete_old_id,
11439 new_id: zero,
11440 name: "refs/heads/old".into(),
11441 },
11442 ],
11443 capabilities: vec![
11444 Capability {
11445 name: "report-status".into(),
11446 value: None,
11447 },
11448 Capability {
11449 name: "side-band-64k".into(),
11450 value: None,
11451 },
11452 Capability {
11453 name: "agent".into(),
11454 value: Some("git/2.54.0".into()),
11455 },
11456 ],
11457 }
11458 );
11459 assert_eq!(
11460 encode_receive_pack_request(&request).expect("test operation should succeed"),
11461 frames
11462 );
11463 assert_eq!(
11464 parse_receive_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
11465 .expect("test operation should succeed"),
11466 ReceivePackRequest::default()
11467 );
11468 }
11469
11470 #[test]
11471 fn receive_pack_request_streams_round_trip() {
11472 let request = ReceivePackRequest {
11473 commands: vec![ReceivePackCommand {
11474 old_id: ObjectId::from_hex(
11475 ObjectFormat::Sha1,
11476 "0000000000000000000000000000000000000000",
11477 )
11478 .expect("test operation should succeed"),
11479 new_id: ObjectId::from_hex(
11480 ObjectFormat::Sha1,
11481 "1111111111111111111111111111111111111111",
11482 )
11483 .expect("test operation should succeed"),
11484 name: "refs/heads/main".into(),
11485 }],
11486 capabilities: vec![Capability {
11487 name: "report-status".into(),
11488 value: None,
11489 }],
11490 ..ReceivePackRequest::default()
11491 };
11492 let mut encoded = Vec::new();
11493 write_receive_pack_request(&mut encoded, &request).expect("test operation should succeed");
11494 encoded.extend_from_slice(b"PACK");
11495
11496 let mut input = encoded.as_slice();
11497 assert_eq!(
11498 read_receive_pack_request(ObjectFormat::Sha1, &mut input)
11499 .expect("test operation should succeed"),
11500 request
11501 );
11502 assert_eq!(input, b"PACK");
11503 }
11504
11505 #[test]
11506 fn receive_pack_request_rejects_malformed_commands() {
11507 assert!(
11508 parse_receive_pack_request(
11509 ObjectFormat::Sha1,
11510 &[PktLineFrame::Data(
11511 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11512 .to_vec(),
11513 )],
11514 )
11515 .is_err()
11516 );
11517 assert!(
11518 parse_receive_pack_request(
11519 ObjectFormat::Sha1,
11520 &[
11521 PktLineFrame::Data(
11522 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11523 .to_vec(),
11524 ),
11525 PktLineFrame::Data(
11526 b"shallow 3333333333333333333333333333333333333333\n".to_vec(),
11527 ),
11528 PktLineFrame::Flush,
11529 ],
11530 )
11531 .is_err()
11532 );
11533 assert!(
11534 parse_receive_pack_request(
11535 ObjectFormat::Sha1,
11536 &[
11537 PktLineFrame::Data(
11538 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status\n"
11539 .to_vec(),
11540 ),
11541 PktLineFrame::Data(
11542 b"3333333333333333333333333333333333333333 4444444444444444444444444444444444444444 refs/heads/next\0side-band-64k\n"
11543 .to_vec(),
11544 ),
11545 PktLineFrame::Flush,
11546 ],
11547 )
11548 .is_err()
11549 );
11550 assert!(
11551 parse_receive_pack_request(
11552 ObjectFormat::Sha1,
11553 &[
11554 PktLineFrame::Data(
11555 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
11556 ),
11557 PktLineFrame::Flush,
11558 ],
11559 )
11560 .is_err()
11561 );
11562 assert!(
11563 encode_receive_pack_request(&ReceivePackRequest {
11564 shallow: vec![
11565 ObjectId::from_hex(
11566 ObjectFormat::Sha1,
11567 "1111111111111111111111111111111111111111",
11568 )
11569 .expect("test operation should succeed")
11570 ],
11571 ..ReceivePackRequest::default()
11572 })
11573 .is_err()
11574 );
11575 assert!(
11576 encode_receive_pack_request(&ReceivePackRequest {
11577 commands: vec![ReceivePackCommand {
11578 old_id: ObjectId::from_hex(
11579 ObjectFormat::Sha1,
11580 "1111111111111111111111111111111111111111",
11581 )
11582 .expect("test operation should succeed"),
11583 new_id: ObjectId::from_hex(
11584 ObjectFormat::Sha1,
11585 "2222222222222222222222222222222222222222",
11586 )
11587 .expect("test operation should succeed"),
11588 name: "bad ref".into(),
11589 }],
11590 ..ReceivePackRequest::default()
11591 })
11592 .is_err()
11593 );
11594 }
11595
11596 #[test]
11597 fn receive_pack_features_parse_encode_and_validate_push_request() {
11598 let capabilities = vec![
11599 Capability {
11600 name: "report-status".into(),
11601 value: None,
11602 },
11603 Capability {
11604 name: "report-status-v2".into(),
11605 value: None,
11606 },
11607 Capability {
11608 name: "delete-refs".into(),
11609 value: None,
11610 },
11611 Capability {
11612 name: "ofs-delta".into(),
11613 value: None,
11614 },
11615 Capability {
11616 name: "atomic".into(),
11617 value: None,
11618 },
11619 Capability {
11620 name: "push-options".into(),
11621 value: None,
11622 },
11623 Capability {
11624 name: "side-band-64k".into(),
11625 value: None,
11626 },
11627 Capability {
11628 name: "quiet".into(),
11629 value: None,
11630 },
11631 Capability {
11632 name: "no-thin".into(),
11633 value: None,
11634 },
11635 Capability {
11636 name: "agent".into(),
11637 value: Some("git/2.54.0".into()),
11638 },
11639 Capability {
11640 name: "object-format".into(),
11641 value: Some("sha256".into()),
11642 },
11643 Capability {
11644 name: "custom".into(),
11645 value: Some("value".into()),
11646 },
11647 ];
11648 let features =
11649 parse_receive_pack_features(&capabilities).expect("test operation should succeed");
11650 assert_eq!(
11651 features,
11652 ReceivePackFeatures {
11653 report_status: true,
11654 report_status_v2: true,
11655 delete_refs: true,
11656 ofs_delta: true,
11657 atomic: true,
11658 push_options: true,
11659 side_band_64k: true,
11660 quiet: true,
11661 no_thin: true,
11662 agent: Some("git/2.54.0".into()),
11663 object_format: Some(ObjectFormat::Sha256),
11664 unknown: vec![Capability {
11665 name: "custom".into(),
11666 value: Some("value".into()),
11667 }],
11668 }
11669 );
11670 assert_eq!(
11671 encode_receive_pack_features(&features).expect("test operation should succeed"),
11672 capabilities
11673 );
11674
11675 let request = ReceivePackPushRequest {
11676 commands: ReceivePackRequest {
11677 commands: vec![ReceivePackCommand {
11678 old_id: ObjectId::from_hex(
11679 ObjectFormat::Sha1,
11680 "1111111111111111111111111111111111111111",
11681 )
11682 .expect("test operation should succeed"),
11683 new_id: ObjectId::from_hex(
11684 ObjectFormat::Sha1,
11685 "2222222222222222222222222222222222222222",
11686 )
11687 .expect("test operation should succeed"),
11688 name: "refs/heads/main".into(),
11689 }],
11690 capabilities: vec![
11691 Capability {
11692 name: "report-status".into(),
11693 value: None,
11694 },
11695 Capability {
11696 name: "ofs-delta".into(),
11697 value: None,
11698 },
11699 Capability {
11700 name: "push-options".into(),
11701 value: None,
11702 },
11703 Capability {
11704 name: "side-band-64k".into(),
11705 value: None,
11706 },
11707 Capability {
11708 name: "agent".into(),
11709 value: Some("sley".into()),
11710 },
11711 ],
11712 ..ReceivePackRequest::default()
11713 },
11714 push_options: Some(vec!["ci.skip".into()]),
11715 packfile: b"PACKpayload".to_vec(),
11716 };
11717 validate_receive_pack_push_request_features(&features, &request)
11718 .expect("test operation should succeed");
11719 }
11720
11721 #[test]
11722 fn receive_pack_features_reject_invalid_push_requests() {
11723 let old_id = ObjectId::from_hex(
11724 ObjectFormat::Sha1,
11725 "1111111111111111111111111111111111111111",
11726 )
11727 .expect("test operation should succeed");
11728 let new_id = ObjectId::from_hex(
11729 ObjectFormat::Sha1,
11730 "2222222222222222222222222222222222222222",
11731 )
11732 .expect("test operation should succeed");
11733 let zero = ObjectId::from_hex(
11734 ObjectFormat::Sha1,
11735 "0000000000000000000000000000000000000000",
11736 )
11737 .expect("test operation should succeed");
11738 let features = ReceivePackFeatures {
11739 report_status: true,
11740 push_options: true,
11741 ..ReceivePackFeatures::default()
11742 };
11743 let update = ReceivePackCommand {
11744 old_id: old_id.clone(),
11745 new_id: new_id.clone(),
11746 name: "refs/heads/main".into(),
11747 };
11748
11749 assert!(
11750 validate_receive_pack_push_request_features(
11751 &features,
11752 &ReceivePackPushRequest {
11753 commands: ReceivePackRequest {
11754 commands: vec![update.clone()],
11755 capabilities: vec![Capability {
11756 name: "push-options".into(),
11757 value: None,
11758 }],
11759 ..ReceivePackRequest::default()
11760 },
11761 push_options: None,
11762 packfile: b"PACKpayload".to_vec(),
11763 },
11764 )
11765 .is_err()
11766 );
11767 assert!(
11768 validate_receive_pack_push_request_features(
11769 &features,
11770 &ReceivePackPushRequest {
11771 commands: ReceivePackRequest {
11772 commands: vec![update.clone()],
11773 ..ReceivePackRequest::default()
11774 },
11775 push_options: Some(Vec::new()),
11776 packfile: b"PACKpayload".to_vec(),
11777 },
11778 )
11779 .is_err()
11780 );
11781 assert!(
11782 validate_receive_pack_push_request_features(
11783 &features,
11784 &ReceivePackPushRequest {
11785 commands: ReceivePackRequest {
11786 commands: vec![ReceivePackCommand {
11787 old_id: old_id.clone(),
11788 new_id: zero.clone(),
11789 name: "refs/heads/main".into(),
11790 }],
11791 ..ReceivePackRequest::default()
11792 },
11793 push_options: None,
11794 packfile: Vec::new(),
11795 },
11796 )
11797 .is_err()
11798 );
11799 validate_receive_pack_push_request_features(
11800 &features,
11801 &ReceivePackPushRequest {
11802 commands: ReceivePackRequest {
11803 commands: vec![update.clone()],
11804 ..ReceivePackRequest::default()
11805 },
11806 push_options: None,
11807 packfile: Vec::new(),
11808 },
11809 )
11810 .expect("updates to already-present objects may omit a packfile");
11811 assert!(
11812 validate_receive_pack_push_request_features(
11813 &ReceivePackFeatures {
11814 delete_refs: true,
11815 ..ReceivePackFeatures::default()
11816 },
11817 &ReceivePackPushRequest {
11818 commands: ReceivePackRequest {
11819 commands: vec![ReceivePackCommand {
11820 old_id,
11821 new_id: zero,
11822 name: "refs/heads/main".into(),
11823 }],
11824 ..ReceivePackRequest::default()
11825 },
11826 push_options: None,
11827 packfile: b"PACKpayload".to_vec(),
11828 },
11829 )
11830 .is_err()
11831 );
11832 assert!(
11833 validate_receive_pack_push_request_features(
11834 &features,
11835 &ReceivePackPushRequest {
11836 commands: ReceivePackRequest {
11837 commands: vec![update],
11838 capabilities: vec![Capability {
11839 name: "atomic".into(),
11840 value: None,
11841 }],
11842 ..ReceivePackRequest::default()
11843 },
11844 push_options: None,
11845 packfile: b"PACKpayload".to_vec(),
11846 },
11847 )
11848 .is_err()
11849 );
11850
11851 assert!(
11852 parse_receive_pack_features(&[
11853 Capability {
11854 name: "push-options".into(),
11855 value: None,
11856 },
11857 Capability {
11858 name: "push-options".into(),
11859 value: None,
11860 },
11861 ])
11862 .is_err()
11863 );
11864 assert!(
11865 encode_receive_pack_features(&ReceivePackFeatures {
11866 unknown: vec![Capability {
11867 name: "atomic".into(),
11868 value: None,
11869 }],
11870 ..ReceivePackFeatures::default()
11871 })
11872 .is_err()
11873 );
11874 }
11875
11876 #[test]
11877 fn receive_pack_apply_helper_installs_pack_verifies_objects_and_reports_ok() {
11878 let old_id = ObjectId::from_hex(
11879 ObjectFormat::Sha1,
11880 "1111111111111111111111111111111111111111",
11881 )
11882 .expect("test operation should succeed");
11883 let new_id = ObjectId::from_hex(
11884 ObjectFormat::Sha1,
11885 "2222222222222222222222222222222222222222",
11886 )
11887 .expect("test operation should succeed");
11888 let request = ReceivePackPushRequest {
11889 commands: ReceivePackRequest {
11890 commands: vec![ReceivePackCommand {
11891 old_id: old_id.clone(),
11892 new_id: new_id.clone(),
11893 name: "refs/heads/main".into(),
11894 }],
11895 ..ReceivePackRequest::default()
11896 },
11897 packfile: b"PACKpayload".to_vec(),
11898 ..ReceivePackPushRequest::default()
11899 };
11900 let installed = std::cell::Cell::new(false);
11901 let applied = std::cell::RefCell::new(Vec::new());
11902
11903 let report = apply_receive_pack_push_request(
11904 &ReceivePackFeatures::default(),
11905 &request,
11906 |_| unreachable!("update stale-old checks belong to the ref transaction callback"),
11907 |packfile| {
11908 assert_eq!(packfile, b"PACKpayload");
11909 installed.set(true);
11910 Ok(())
11911 },
11912 |oid| Ok(oid == &new_id),
11913 |commands| {
11914 applied.borrow_mut().extend_from_slice(commands);
11915 Ok(())
11916 },
11917 |_| unreachable!("no delete command should be applied"),
11918 )
11919 .expect("test operation should succeed");
11920
11921 assert!(installed.get());
11922 assert_eq!(applied.into_inner(), request.commands.commands);
11923 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11924 assert_eq!(
11925 report.commands,
11926 vec![ReceivePackCommandStatus::Ok {
11927 name: "refs/heads/main".into(),
11928 }]
11929 );
11930 }
11931
11932 #[test]
11933 fn receive_pack_apply_helper_allows_update_without_pack_when_object_exists() {
11934 let old_id = ObjectId::from_hex(
11935 ObjectFormat::Sha1,
11936 "1111111111111111111111111111111111111111",
11937 )
11938 .expect("test operation should succeed");
11939 let new_id = ObjectId::from_hex(
11940 ObjectFormat::Sha1,
11941 "2222222222222222222222222222222222222222",
11942 )
11943 .expect("test operation should succeed");
11944 let request = ReceivePackPushRequest {
11945 commands: ReceivePackRequest {
11946 commands: vec![ReceivePackCommand {
11947 old_id: old_id.clone(),
11948 new_id: new_id.clone(),
11949 name: "refs/heads/main".into(),
11950 }],
11951 ..ReceivePackRequest::default()
11952 },
11953 ..ReceivePackPushRequest::default()
11954 };
11955 let installed = std::cell::Cell::new(false);
11956 let applied = std::cell::RefCell::new(Vec::new());
11957
11958 let report = apply_receive_pack_push_request(
11959 &ReceivePackFeatures::default(),
11960 &request,
11961 |_| unreachable!("update stale-old checks belong to the ref transaction callback"),
11962 |_| {
11963 installed.set(true);
11964 Ok(())
11965 },
11966 |oid| Ok(oid == &new_id),
11967 |commands| {
11968 applied.borrow_mut().extend_from_slice(commands);
11969 Ok(())
11970 },
11971 |_| unreachable!("no delete command should be applied"),
11972 )
11973 .expect("test operation should succeed");
11974
11975 assert!(!installed.get());
11976 assert_eq!(applied.into_inner(), request.commands.commands);
11977 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11978 }
11979
11980 #[test]
11981 fn receive_pack_apply_helper_preserves_delete_only_and_stale_delete_rules() {
11982 let old_id = ObjectId::from_hex(
11983 ObjectFormat::Sha1,
11984 "1111111111111111111111111111111111111111",
11985 )
11986 .expect("test operation should succeed");
11987 let other_id = ObjectId::from_hex(
11988 ObjectFormat::Sha1,
11989 "2222222222222222222222222222222222222222",
11990 )
11991 .expect("test operation should succeed");
11992 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
11993 let request = ReceivePackPushRequest {
11994 commands: ReceivePackRequest {
11995 commands: vec![ReceivePackCommand {
11996 old_id: old_id.clone(),
11997 new_id: zero,
11998 name: "refs/heads/main".into(),
11999 }],
12000 ..ReceivePackRequest::default()
12001 },
12002 ..ReceivePackPushRequest::default()
12003 };
12004 let features = ReceivePackFeatures {
12005 delete_refs: true,
12006 ..ReceivePackFeatures::default()
12007 };
12008 let installed = std::cell::Cell::new(false);
12009 let deleted = std::cell::RefCell::new(Vec::new());
12010
12011 let report = apply_receive_pack_push_request(
12012 &features,
12013 &request,
12014 |_| Ok(Some(old_id.clone())),
12015 |_| {
12016 installed.set(true);
12017 Ok(())
12018 },
12019 |_| Ok(false),
12020 |_| unreachable!("delete-only request should not apply updates"),
12021 |command| {
12022 deleted.borrow_mut().push(command.name.clone());
12023 Ok(())
12024 },
12025 )
12026 .expect("test operation should succeed");
12027
12028 assert!(!installed.get());
12029 assert_eq!(deleted.into_inner(), vec!["refs/heads/main"]);
12030 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
12031 assert!(
12032 apply_receive_pack_push_request(
12033 &features,
12034 &request,
12035 |_| Ok(Some(other_id.clone())),
12036 |_| Ok(()),
12037 |_| Ok(false),
12038 |_| Ok(()),
12039 |_| Ok(()),
12040 )
12041 .is_err()
12042 );
12043 }
12044
12045 #[test]
12046 fn receive_pack_push_request_parses_commands_options_and_packfile() {
12047 let command = ReceivePackCommand {
12048 old_id: ObjectId::from_hex(
12049 ObjectFormat::Sha1,
12050 "1111111111111111111111111111111111111111",
12051 )
12052 .expect("test operation should succeed"),
12053 new_id: ObjectId::from_hex(
12054 ObjectFormat::Sha1,
12055 "2222222222222222222222222222222222222222",
12056 )
12057 .expect("test operation should succeed"),
12058 name: "refs/heads/main".into(),
12059 };
12060 let expected = ReceivePackPushRequest {
12061 commands: ReceivePackRequest {
12062 commands: vec![command],
12063 capabilities: vec![
12064 Capability {
12065 name: "report-status".into(),
12066 value: None,
12067 },
12068 Capability {
12069 name: "push-options".into(),
12070 value: None,
12071 },
12072 ],
12073 ..ReceivePackRequest::default()
12074 },
12075 push_options: Some(vec!["ci.skip".into(), "deploy=staging".into()]),
12076 packfile: b"PACK\x00\x00\x00\x02payload".to_vec(),
12077 };
12078 let encoded =
12079 encode_receive_pack_push_request(&expected).expect("test operation should succeed");
12080
12081 assert_eq!(
12082 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true)
12083 .expect("test operation should succeed"),
12084 expected
12085 );
12086 }
12087
12088 #[test]
12089 fn receive_pack_push_request_preserves_packfile_without_push_options() {
12090 let request = ReceivePackPushRequest {
12091 commands: ReceivePackRequest {
12092 commands: vec![ReceivePackCommand {
12093 old_id: ObjectId::from_hex(
12094 ObjectFormat::Sha1,
12095 "1111111111111111111111111111111111111111",
12096 )
12097 .expect("test operation should succeed"),
12098 new_id: ObjectId::from_hex(
12099 ObjectFormat::Sha1,
12100 "2222222222222222222222222222222222222222",
12101 )
12102 .expect("test operation should succeed"),
12103 name: "refs/heads/main".into(),
12104 }],
12105 ..ReceivePackRequest::default()
12106 },
12107 push_options: None,
12108 packfile: b"0000PACK-like bytes stay raw".to_vec(),
12109 };
12110 let encoded =
12111 encode_receive_pack_push_request(&request).expect("test operation should succeed");
12112
12113 assert_eq!(
12114 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, false)
12115 .expect("test operation should succeed"),
12116 request
12117 );
12118 }
12119
12120 #[test]
12121 fn receive_pack_push_request_streams_round_trip() {
12122 let request = ReceivePackPushRequest {
12123 commands: ReceivePackRequest {
12124 commands: vec![ReceivePackCommand {
12125 old_id: ObjectId::from_hex(
12126 ObjectFormat::Sha1,
12127 "1111111111111111111111111111111111111111",
12128 )
12129 .expect("test operation should succeed"),
12130 new_id: ObjectId::from_hex(
12131 ObjectFormat::Sha1,
12132 "2222222222222222222222222222222222222222",
12133 )
12134 .expect("test operation should succeed"),
12135 name: "refs/heads/main".into(),
12136 }],
12137 capabilities: vec![Capability {
12138 name: "push-options".into(),
12139 value: None,
12140 }],
12141 ..ReceivePackRequest::default()
12142 },
12143 push_options: Some(Vec::new()),
12144 packfile: b"PACKpayload".to_vec(),
12145 };
12146 let mut encoded = Vec::new();
12147 write_receive_pack_push_request(&mut encoded, &request)
12148 .expect("test operation should succeed");
12149
12150 assert_eq!(
12151 read_receive_pack_push_request(ObjectFormat::Sha1, &mut encoded.as_slice(), true)
12152 .expect("test operation should succeed"),
12153 request
12154 );
12155 }
12156
12157 #[test]
12158 fn receive_pack_push_request_rejects_malformed_sections() {
12159 assert!(
12160 parse_receive_pack_push_request(
12161 ObjectFormat::Sha1,
12162 b"0014not-a-command\n0000PACK",
12163 false,
12164 )
12165 .is_err()
12166 );
12167
12168 let request = ReceivePackPushRequest {
12169 commands: ReceivePackRequest {
12170 commands: vec![ReceivePackCommand {
12171 old_id: ObjectId::from_hex(
12172 ObjectFormat::Sha1,
12173 "1111111111111111111111111111111111111111",
12174 )
12175 .expect("test operation should succeed"),
12176 new_id: ObjectId::from_hex(
12177 ObjectFormat::Sha1,
12178 "2222222222222222222222222222222222222222",
12179 )
12180 .expect("test operation should succeed"),
12181 name: "refs/heads/main".into(),
12182 }],
12183 ..ReceivePackRequest::default()
12184 },
12185 push_options: None,
12186 packfile: b"PACKpayload".to_vec(),
12187 };
12188 let encoded =
12189 encode_receive_pack_push_request(&request).expect("test operation should succeed");
12190 assert!(parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true).is_err());
12191
12192 assert!(
12193 encode_receive_pack_push_request(&ReceivePackPushRequest {
12194 commands: ReceivePackRequest {
12195 shallow: vec![
12196 ObjectId::from_hex(
12197 ObjectFormat::Sha1,
12198 "1111111111111111111111111111111111111111",
12199 )
12200 .expect("test operation should succeed")
12201 ],
12202 ..ReceivePackRequest::default()
12203 },
12204 push_options: None,
12205 packfile: Vec::new(),
12206 })
12207 .is_err()
12208 );
12209 }
12210
12211 #[test]
12212 fn receive_pack_report_status_parses_and_encodes_status_lines() {
12213 let frames = vec![
12214 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12215 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12216 PktLineFrame::Data(b"ng refs/heads/old non-fast-forward\n".to_vec()),
12217 PktLineFrame::Flush,
12218 ];
12219 let report =
12220 parse_receive_pack_report_status(&frames).expect("test operation should succeed");
12221 assert_eq!(
12222 report,
12223 ReceivePackReportStatus {
12224 unpack: ReceivePackUnpackStatus::Ok,
12225 commands: vec![
12226 ReceivePackCommandStatus::Ok {
12227 name: "refs/heads/main".into(),
12228 },
12229 ReceivePackCommandStatus::Ng {
12230 name: "refs/heads/old".into(),
12231 message: "non-fast-forward".into(),
12232 },
12233 ],
12234 }
12235 );
12236 assert_eq!(
12237 encode_receive_pack_report_status(&report).expect("test operation should succeed"),
12238 frames
12239 );
12240
12241 let frames = vec![
12242 PktLineFrame::Data(b"unpack pack exceeds maximum size\n".to_vec()),
12243 PktLineFrame::Flush,
12244 ];
12245 assert_eq!(
12246 parse_receive_pack_report_status(&frames).expect("test operation should succeed"),
12247 ReceivePackReportStatus {
12248 unpack: ReceivePackUnpackStatus::Error("pack exceeds maximum size".into()),
12249 commands: Vec::new(),
12250 }
12251 );
12252 }
12253
12254 #[test]
12255 fn receive_pack_report_status_streams_round_trip() {
12256 let report = ReceivePackReportStatus {
12257 unpack: ReceivePackUnpackStatus::Ok,
12258 commands: vec![ReceivePackCommandStatus::Ok {
12259 name: "refs/heads/main".into(),
12260 }],
12261 };
12262 let mut encoded = Vec::new();
12263 write_receive_pack_report_status(&mut encoded, &report)
12264 .expect("test operation should succeed");
12265 encoded.extend_from_slice(b"tail");
12266
12267 let mut input = encoded.as_slice();
12268 assert_eq!(
12269 read_receive_pack_report_status(&mut input).expect("test operation should succeed"),
12270 report
12271 );
12272 assert_eq!(input, b"tail");
12273 }
12274
12275 #[test]
12276 fn receive_pack_report_status_rejects_malformed_status_lines() {
12277 assert!(parse_receive_pack_report_status(&[]).is_err());
12278 assert!(
12279 parse_receive_pack_report_status(&[
12280 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12281 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12282 ])
12283 .is_err()
12284 );
12285 assert!(
12286 parse_receive_pack_report_status(&[
12287 PktLineFrame::Flush,
12288 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12289 ])
12290 .is_err()
12291 );
12292 assert!(
12293 parse_receive_pack_report_status(&[
12294 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12295 PktLineFrame::Data(b"bad refs/heads/main\n".to_vec()),
12296 PktLineFrame::Flush,
12297 ])
12298 .is_err()
12299 );
12300 assert!(
12301 parse_receive_pack_report_status(&[
12302 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12303 PktLineFrame::Data(b"ng refs/heads/main\n".to_vec()),
12304 PktLineFrame::Flush,
12305 ])
12306 .is_err()
12307 );
12308 assert!(
12309 encode_receive_pack_report_status(&ReceivePackReportStatus {
12310 unpack: ReceivePackUnpackStatus::Error("".into()),
12311 commands: Vec::new(),
12312 })
12313 .is_err()
12314 );
12315 assert!(
12316 encode_receive_pack_report_status(&ReceivePackReportStatus {
12317 unpack: ReceivePackUnpackStatus::Ok,
12318 commands: vec![ReceivePackCommandStatus::Ok {
12319 name: "bad ref".into(),
12320 }],
12321 })
12322 .is_err()
12323 );
12324 }
12325
12326 #[test]
12327 fn receive_pack_report_status_v2_parses_and_encodes_options() {
12328 let old_oid = ObjectId::from_hex(
12329 ObjectFormat::Sha1,
12330 "1111111111111111111111111111111111111111",
12331 )
12332 .expect("test operation should succeed");
12333 let new_oid = ObjectId::from_hex(
12334 ObjectFormat::Sha1,
12335 "2222222222222222222222222222222222222222",
12336 )
12337 .expect("test operation should succeed");
12338 let frames = vec![
12339 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12340 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12341 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12342 PktLineFrame::Data(
12343 b"option old-oid 1111111111111111111111111111111111111111\n".to_vec(),
12344 ),
12345 PktLineFrame::Data(
12346 b"option new-oid 2222222222222222222222222222222222222222\n".to_vec(),
12347 ),
12348 PktLineFrame::Data(b"option forced-update\n".to_vec()),
12349 PktLineFrame::Data(b"ng refs/heads/old rejected by hook\n".to_vec()),
12350 PktLineFrame::Flush,
12351 ];
12352 let report = parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &frames)
12353 .expect("test operation should succeed");
12354 assert_eq!(
12355 report,
12356 ReceivePackReportStatusV2 {
12357 unpack: ReceivePackUnpackStatus::Ok,
12358 commands: vec![
12359 ReceivePackCommandStatusV2::Ok {
12360 name: "refs/for/main".into(),
12361 options: ReceivePackCommandStatusV2Options {
12362 refname: Some("refs/heads/main".into()),
12363 old_oid: Some(old_oid),
12364 new_oid: Some(new_oid),
12365 forced_update: true,
12366 },
12367 },
12368 ReceivePackCommandStatusV2::Ng {
12369 name: "refs/heads/old".into(),
12370 message: "rejected by hook".into(),
12371 },
12372 ],
12373 }
12374 );
12375 assert_eq!(
12376 encode_receive_pack_report_status_v2(&report).expect("test operation should succeed"),
12377 frames
12378 );
12379 }
12380
12381 #[test]
12382 fn receive_pack_report_status_v2_streams_round_trip() {
12383 let report = ReceivePackReportStatusV2 {
12384 unpack: ReceivePackUnpackStatus::Ok,
12385 commands: vec![ReceivePackCommandStatusV2::Ok {
12386 name: "refs/for/main".into(),
12387 options: ReceivePackCommandStatusV2Options {
12388 refname: Some("refs/heads/main".into()),
12389 old_oid: None,
12390 new_oid: None,
12391 forced_update: false,
12392 },
12393 }],
12394 };
12395 let mut encoded = Vec::new();
12396 write_receive_pack_report_status_v2(&mut encoded, &report)
12397 .expect("test operation should succeed");
12398 encoded.extend_from_slice(b"tail");
12399
12400 let mut input = encoded.as_slice();
12401 assert_eq!(
12402 read_receive_pack_report_status_v2(ObjectFormat::Sha1, &mut input)
12403 .expect("test operation should succeed"),
12404 report
12405 );
12406 assert_eq!(input, b"tail");
12407 }
12408
12409 #[test]
12410 fn receive_pack_report_status_v2_rejects_malformed_options() {
12411 assert!(parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &[]).is_err());
12412 assert!(
12413 parse_receive_pack_report_status_v2(
12414 ObjectFormat::Sha1,
12415 &[
12416 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12417 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12418 PktLineFrame::Flush,
12419 ],
12420 )
12421 .is_err()
12422 );
12423 assert!(
12424 parse_receive_pack_report_status_v2(
12425 ObjectFormat::Sha1,
12426 &[
12427 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12428 PktLineFrame::Data(b"ng refs/heads/main rejected\n".to_vec()),
12429 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12430 PktLineFrame::Flush,
12431 ],
12432 )
12433 .is_err()
12434 );
12435 assert!(
12436 parse_receive_pack_report_status_v2(
12437 ObjectFormat::Sha1,
12438 &[
12439 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12440 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12441 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12442 PktLineFrame::Data(b"option refname refs/heads/next\n".to_vec()),
12443 PktLineFrame::Flush,
12444 ],
12445 )
12446 .is_err()
12447 );
12448 assert!(
12449 parse_receive_pack_report_status_v2(
12450 ObjectFormat::Sha1,
12451 &[
12452 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12453 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12454 PktLineFrame::Data(b"option old-oid not-an-oid\n".to_vec()),
12455 PktLineFrame::Flush,
12456 ],
12457 )
12458 .is_err()
12459 );
12460 assert!(
12461 encode_receive_pack_report_status_v2(&ReceivePackReportStatusV2 {
12462 unpack: ReceivePackUnpackStatus::Ok,
12463 commands: vec![ReceivePackCommandStatusV2::Ok {
12464 name: "refs/for/main".into(),
12465 options: ReceivePackCommandStatusV2Options {
12466 refname: Some("bad ref".into()),
12467 ..ReceivePackCommandStatusV2Options::default()
12468 },
12469 }],
12470 })
12471 .is_err()
12472 );
12473 }
12474
12475 #[test]
12476 fn receive_pack_push_options_parse_and_encode_options() {
12477 let frames = vec![
12478 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12479 PktLineFrame::Data(b"deploy target=staging\n".to_vec()),
12480 PktLineFrame::Data(b"\n".to_vec()),
12481 PktLineFrame::Flush,
12482 ];
12483 let options =
12484 parse_receive_pack_push_options(&frames).expect("test operation should succeed");
12485 assert_eq!(
12486 options,
12487 vec![
12488 "ci.skip".to_string(),
12489 "deploy target=staging".to_string(),
12490 String::new(),
12491 ]
12492 );
12493 assert_eq!(
12494 encode_receive_pack_push_options(&options).expect("test operation should succeed"),
12495 frames
12496 );
12497 assert_eq!(
12498 parse_receive_pack_push_options(&[PktLineFrame::Flush])
12499 .expect("test operation should succeed"),
12500 Vec::<String>::new()
12501 );
12502 }
12503
12504 #[test]
12505 fn receive_pack_push_options_streams_round_trip() {
12506 let options = vec!["ci.skip".to_string(), "reviewer=alice".to_string()];
12507 let mut encoded = Vec::new();
12508 write_receive_pack_push_options(&mut encoded, &options)
12509 .expect("test operation should succeed");
12510 encoded.extend_from_slice(b"PACK");
12511
12512 let mut input = encoded.as_slice();
12513 assert_eq!(
12514 read_receive_pack_push_options(&mut input).expect("test operation should succeed"),
12515 options
12516 );
12517 assert_eq!(input, b"PACK");
12518 }
12519
12520 #[test]
12521 fn receive_pack_push_options_reject_malformed_streams() {
12522 assert!(
12523 parse_receive_pack_push_options(&[PktLineFrame::Data(b"ci.skip\n".to_vec())]).is_err()
12524 );
12525 assert!(
12526 parse_receive_pack_push_options(&[PktLineFrame::Delimiter, PktLineFrame::Flush])
12527 .is_err()
12528 );
12529 assert!(
12530 parse_receive_pack_push_options(&[
12531 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12532 PktLineFrame::Flush,
12533 PktLineFrame::Data(b"after\n".to_vec()),
12534 ])
12535 .is_err()
12536 );
12537 assert!(
12538 parse_receive_pack_push_options(&[
12539 PktLineFrame::Data(b"bad\0option\n".to_vec()),
12540 PktLineFrame::Flush,
12541 ])
12542 .is_err()
12543 );
12544 assert!(encode_receive_pack_push_options(&["bad\noption".to_string()]).is_err());
12545 }
12546
12547 #[test]
12548 fn protocol_v2_advertisement_parses_version_and_capabilities() {
12549 let frames = parse_pkt_line_stream(
12550 b"000eversion 2\n0015agent=git/2.54.0\n0013ls-refs=unborn\n0027fetch=shallow wait-for-done filter\n0012server-option\n0000",
12551 )
12552 .expect("test operation should succeed");
12553 let handshake =
12554 parse_protocol_v2_advertisement(&frames).expect("test operation should succeed");
12555 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12556 assert_eq!(
12557 handshake.capabilities,
12558 vec![
12559 Capability {
12560 name: "agent".into(),
12561 value: Some("git/2.54.0".into()),
12562 },
12563 Capability {
12564 name: "ls-refs".into(),
12565 value: Some("unborn".into()),
12566 },
12567 Capability {
12568 name: "fetch".into(),
12569 value: Some("shallow wait-for-done filter".into()),
12570 },
12571 Capability {
12572 name: "server-option".into(),
12573 value: None,
12574 },
12575 ]
12576 );
12577 assert_eq!(
12578 encode_protocol_v2_advertisement(&handshake).expect("test operation should succeed"),
12579 frames
12580 );
12581 }
12582
12583 #[test]
12584 fn protocol_v2_advertisement_reads_until_flush() {
12585 let mut input = b"000eversion 2\n0013ls-refs=unborn\n0000next-session".as_slice();
12586 let handshake =
12587 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed");
12588 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12589 assert_eq!(
12590 handshake.capabilities,
12591 vec![Capability {
12592 name: "ls-refs".into(),
12593 value: Some("unborn".into()),
12594 }]
12595 );
12596 assert_eq!(input, b"next-session");
12597 }
12598
12599 #[test]
12600 fn protocol_v2_advertisement_writes_stream() {
12601 let handshake = TransportHandshake {
12602 protocol: ProtocolVersion::V2,
12603 capabilities: vec![
12604 Capability {
12605 name: "agent".into(),
12606 value: Some("sley/0".into()),
12607 },
12608 Capability {
12609 name: "fetch".into(),
12610 value: Some("shallow filter".into()),
12611 },
12612 ],
12613 };
12614 let mut encoded = Vec::new();
12615 write_protocol_v2_advertisement(&mut encoded, &handshake)
12616 .expect("test operation should succeed");
12617 let mut input = encoded.as_slice();
12618 assert_eq!(
12619 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed"),
12620 handshake
12621 );
12622 assert!(input.is_empty());
12623 assert!(
12624 encode_protocol_v2_advertisement(&TransportHandshake {
12625 protocol: ProtocolVersion::V1,
12626 capabilities: Vec::new(),
12627 })
12628 .is_err()
12629 );
12630 }
12631
12632 #[test]
12633 fn protocol_v2_advertisement_rejects_malformed_sequences() {
12634 assert!(parse_protocol_v2_advertisement(&[]).is_err());
12635 assert!(
12636 parse_protocol_v2_advertisement(&[
12637 PktLineFrame::Data(b"version 1\n".to_vec()),
12638 PktLineFrame::Flush,
12639 ])
12640 .is_err()
12641 );
12642 assert!(
12643 parse_protocol_v2_advertisement(&[PktLineFrame::Data(b"version 2\n".to_vec())])
12644 .is_err()
12645 );
12646 assert!(
12647 parse_protocol_v2_advertisement(&[
12648 PktLineFrame::Data(b"version 2\n".to_vec()),
12649 PktLineFrame::Delimiter,
12650 ])
12651 .is_err()
12652 );
12653 assert!(
12654 parse_protocol_v2_advertisement(&[
12655 PktLineFrame::Data(b"version 2\n".to_vec()),
12656 PktLineFrame::Data(b"fetch=\n".to_vec()),
12657 PktLineFrame::Flush,
12658 ])
12659 .is_err()
12660 );
12661 }
12662
12663 #[test]
12664 fn protocol_v2_command_request_parses_and_encodes_sections() {
12665 let frames = parse_pkt_line_stream(
12666 b"0014command=ls-refs\n0011agent=sley/0\n0017object-format=sha1\n00010009peel\n000csymrefs\n001bref-prefix refs/heads/\n0000",
12667 )
12668 .expect("test operation should succeed");
12669 let request =
12670 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12671 assert_eq!(
12672 request,
12673 ProtocolV2CommandRequest {
12674 command: "ls-refs".into(),
12675 capabilities: vec![
12676 Capability {
12677 name: "agent".into(),
12678 value: Some("sley/0".into()),
12679 },
12680 Capability {
12681 name: "object-format".into(),
12682 value: Some("sha1".into()),
12683 },
12684 ],
12685 arguments: vec![
12686 b"peel".to_vec(),
12687 b"symrefs".to_vec(),
12688 b"ref-prefix refs/heads/".to_vec(),
12689 ],
12690 }
12691 );
12692 assert_eq!(
12693 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12694 frames
12695 );
12696 }
12697
12698 #[test]
12699 fn protocol_v2_command_request_allows_no_argument_section() {
12700 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12701 .expect("test operation should succeed");
12702 let request =
12703 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12704 assert_eq!(
12705 request,
12706 ProtocolV2CommandRequest {
12707 command: "fetch".into(),
12708 capabilities: Vec::new(),
12709 arguments: Vec::new(),
12710 }
12711 );
12712 assert_eq!(
12713 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12714 frames
12715 );
12716 }
12717
12718 #[test]
12719 fn protocol_v2_request_parses_commands_and_empty_done() {
12720 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12721 .expect("test operation should succeed");
12722 let command = ProtocolV2CommandRequest {
12723 command: "fetch".into(),
12724 capabilities: Vec::new(),
12725 arguments: Vec::new(),
12726 };
12727 assert_eq!(
12728 parse_protocol_v2_request(&frames).expect("test operation should succeed"),
12729 ProtocolV2Request::Command(command.clone())
12730 );
12731 assert_eq!(
12732 encode_protocol_v2_request(&ProtocolV2Request::Command(command))
12733 .expect("test operation should succeed"),
12734 frames
12735 );
12736
12737 assert_eq!(
12738 parse_protocol_v2_request(&[PktLineFrame::Flush])
12739 .expect("test operation should succeed"),
12740 ProtocolV2Request::Done
12741 );
12742 assert_eq!(
12743 encode_protocol_v2_request(&ProtocolV2Request::Done)
12744 .expect("test operation should succeed"),
12745 vec![PktLineFrame::Flush]
12746 );
12747 }
12748
12749 #[test]
12750 fn protocol_v2_request_streams_empty_done() {
12751 let mut encoded = Vec::new();
12752 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
12753 .expect("test operation should succeed");
12754 encoded.extend_from_slice(b"tail");
12755
12756 let mut input = encoded.as_slice();
12757 assert_eq!(
12758 read_protocol_v2_request(&mut input).expect("test operation should succeed"),
12759 ProtocolV2Request::Done
12760 );
12761 assert_eq!(input, b"tail");
12762 let mut command_input = encoded.as_slice();
12763 assert!(read_protocol_v2_command_request(&mut command_input).is_err());
12764 }
12765
12766 #[test]
12767 fn protocol_v2_command_request_streams_round_trip() {
12768 let request = ProtocolV2CommandRequest {
12769 command: "ls-refs".into(),
12770 capabilities: vec![Capability {
12771 name: "agent".into(),
12772 value: Some("sley/0".into()),
12773 }],
12774 arguments: vec![b"peel".to_vec(), b"symrefs".to_vec()],
12775 };
12776 let mut encoded = Vec::new();
12777 write_protocol_v2_command_request(&mut encoded, &request)
12778 .expect("test operation should succeed");
12779 encoded.extend_from_slice(b"tail");
12780
12781 let mut input = encoded.as_slice();
12782 assert_eq!(
12783 read_protocol_v2_command_request(&mut input).expect("test operation should succeed"),
12784 request
12785 );
12786 assert_eq!(input, b"tail");
12787 }
12788
12789 #[test]
12790 fn protocol_v2_command_request_rejects_malformed_sequences() {
12791 assert!(parse_protocol_v2_command_request(&[]).is_err());
12792 assert!(
12793 parse_protocol_v2_command_request(&[
12794 PktLineFrame::Data(b"agent=sley/0\n".to_vec()),
12795 PktLineFrame::Flush,
12796 ])
12797 .is_err()
12798 );
12799 assert!(
12800 parse_protocol_v2_command_request(&[
12801 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12802 PktLineFrame::Delimiter,
12803 PktLineFrame::Delimiter,
12804 PktLineFrame::Flush,
12805 ])
12806 .is_err()
12807 );
12808 assert!(
12809 parse_protocol_v2_command_request(&[
12810 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12811 PktLineFrame::Delimiter,
12812 PktLineFrame::Data(b"\n".to_vec()),
12813 PktLineFrame::Flush,
12814 ])
12815 .is_err()
12816 );
12817 assert!(
12818 encode_protocol_v2_command_request(&ProtocolV2CommandRequest {
12819 command: "bad command".into(),
12820 capabilities: Vec::new(),
12821 arguments: Vec::new(),
12822 })
12823 .is_err()
12824 );
12825 }
12826
12827 #[test]
12828 fn protocol_v2_ls_refs_request_parses_and_encodes_arguments() {
12829 let command = ProtocolV2CommandRequest {
12830 command: "ls-refs".into(),
12831 capabilities: Vec::new(),
12832 arguments: vec![
12833 b"peel".to_vec(),
12834 b"symrefs".to_vec(),
12835 b"unborn".to_vec(),
12836 b"ref-prefix HEAD".to_vec(),
12837 b"ref-prefix refs/heads/".to_vec(),
12838 ],
12839 };
12840 let request = ProtocolV2LsRefsRequest::from_command_request(&command)
12841 .expect("test operation should succeed");
12842 assert_eq!(
12843 request,
12844 ProtocolV2LsRefsRequest {
12845 peel: true,
12846 symrefs: true,
12847 unborn: true,
12848 ref_prefixes: vec!["HEAD".into(), "refs/heads/".into()],
12849 }
12850 );
12851 assert_eq!(
12852 request
12853 .to_command_request()
12854 .expect("test operation should succeed"),
12855 command
12856 );
12857 assert!(
12858 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12859 command: "fetch".into(),
12860 capabilities: Vec::new(),
12861 arguments: Vec::new(),
12862 })
12863 .is_err()
12864 );
12865 assert!(
12866 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12867 command: "ls-refs".into(),
12868 capabilities: Vec::new(),
12869 arguments: vec![b"ref-prefix ".to_vec()],
12870 })
12871 .is_err()
12872 );
12873 }
12874
12875 #[test]
12876 fn protocol_v2_ls_refs_request_streams_round_trip() {
12877 let request = ProtocolV2LsRefsRequest {
12878 peel: true,
12879 symrefs: true,
12880 unborn: false,
12881 ref_prefixes: vec!["HEAD".into(), "refs/tags/".into()],
12882 };
12883 let mut encoded = Vec::new();
12884 write_protocol_v2_ls_refs_request(&mut encoded, &request)
12885 .expect("test operation should succeed");
12886 encoded.extend_from_slice(b"tail");
12887
12888 let mut input = encoded.as_slice();
12889 assert_eq!(
12890 read_protocol_v2_ls_refs_request(&mut input).expect("test operation should succeed"),
12891 request
12892 );
12893 assert_eq!(input, b"tail");
12894 }
12895
12896 #[test]
12897 fn protocol_v2_ls_refs_response_parses_and_encodes_records() {
12898 let oid = ObjectId::from_hex(
12899 ObjectFormat::Sha1,
12900 "1111111111111111111111111111111111111111",
12901 )
12902 .expect("test operation should succeed");
12903 let peeled = ObjectId::from_hex(
12904 ObjectFormat::Sha1,
12905 "2222222222222222222222222222222222222222",
12906 )
12907 .expect("test operation should succeed");
12908 let frames = vec![
12909 PktLineFrame::Data(
12910 b"1111111111111111111111111111111111111111 refs/tags/v1 peeled:2222222222222222222222222222222222222222 symref-target:refs/heads/main custom\n"
12911 .to_vec(),
12912 ),
12913 PktLineFrame::Data(b"unborn HEAD symref-target:refs/heads/main\n".to_vec()),
12914 PktLineFrame::Flush,
12915 ];
12916 let records = parse_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &frames)
12917 .expect("test operation should succeed");
12918 assert_eq!(
12919 records,
12920 vec![
12921 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12922 oid,
12923 name: "refs/tags/v1".into(),
12924 peeled: Some(peeled),
12925 symref_target: Some("refs/heads/main".into()),
12926 attributes: vec!["custom".into()],
12927 }),
12928 ProtocolV2LsRefsRecord::Unborn {
12929 name: "HEAD".into(),
12930 symref_target: Some("refs/heads/main".into()),
12931 attributes: Vec::new(),
12932 },
12933 ]
12934 );
12935 assert_eq!(
12936 encode_protocol_v2_ls_refs_response(&records).expect("test operation should succeed"),
12937 frames
12938 );
12939 }
12940
12941 #[test]
12942 fn protocol_v2_ls_refs_response_streams_round_trip() {
12943 let oid = ObjectId::from_hex(
12944 ObjectFormat::Sha1,
12945 "1111111111111111111111111111111111111111",
12946 )
12947 .expect("test operation should succeed");
12948 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12949 oid,
12950 name: "refs/heads/main".into(),
12951 peeled: None,
12952 symref_target: Some("refs/heads/trunk".into()),
12953 attributes: vec!["custom".into()],
12954 })];
12955 let mut encoded = Vec::new();
12956 write_protocol_v2_ls_refs_response(&mut encoded, &records)
12957 .expect("test operation should succeed");
12958 encoded.extend_from_slice(b"tail");
12959
12960 let mut input = encoded.as_slice();
12961 assert_eq!(
12962 read_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &mut input)
12963 .expect("test operation should succeed"),
12964 records
12965 );
12966 assert_eq!(input, b"tail");
12967 }
12968
12969 #[test]
12970 fn protocol_v2_ls_refs_response_reads_stateless_response_end() {
12971 let oid = ObjectId::from_hex(
12972 ObjectFormat::Sha1,
12973 "1111111111111111111111111111111111111111",
12974 )
12975 .expect("test operation should succeed");
12976 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12977 oid,
12978 name: "refs/heads/main".into(),
12979 peeled: None,
12980 symref_target: None,
12981 attributes: Vec::new(),
12982 })];
12983 let mut encoded = Vec::new();
12984 write_protocol_v2_ls_refs_response_with_response_end(&mut encoded, &records)
12985 .expect("test operation should succeed");
12986 encoded.extend_from_slice(b"tail");
12987
12988 let mut input = encoded.as_slice();
12989 assert_eq!(
12990 read_protocol_v2_ls_refs_response_until_response_end(ObjectFormat::Sha1, &mut input)
12991 .expect("test operation should succeed"),
12992 records
12993 );
12994 assert_eq!(input, b"tail");
12995 assert!(
12996 parse_protocol_v2_ls_refs_response(
12997 ObjectFormat::Sha1,
12998 &[
12999 PktLineFrame::Data(
13000 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
13001 ),
13002 PktLineFrame::ResponseEnd
13003 ],
13004 )
13005 .is_err()
13006 );
13007 }
13008
13009 #[test]
13010 fn protocol_v2_ls_refs_exchange_writes_request_and_reads_response() {
13011 let oid = ObjectId::from_hex(
13012 ObjectFormat::Sha1,
13013 "1111111111111111111111111111111111111111",
13014 )
13015 .expect("test operation should succeed");
13016 let request = ProtocolV2LsRefsRequest {
13017 peel: true,
13018 symrefs: true,
13019 unborn: false,
13020 ref_prefixes: vec!["refs/heads/".into()],
13021 };
13022 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13023 oid,
13024 name: "refs/heads/main".into(),
13025 peeled: None,
13026 symref_target: None,
13027 attributes: Vec::new(),
13028 })];
13029 let mut response = Vec::new();
13030 write_protocol_v2_ls_refs_response(&mut response, &records)
13031 .expect("test operation should succeed");
13032
13033 let mut input = response.as_slice();
13034 let mut output = Vec::new();
13035 assert_eq!(
13036 exchange_protocol_v2_ls_refs(ObjectFormat::Sha1, &mut input, &mut output, &request)
13037 .expect("test operation should succeed"),
13038 records
13039 );
13040 assert!(input.is_empty());
13041 let mut output_read = output.as_slice();
13042 assert_eq!(
13043 read_protocol_v2_ls_refs_request(&mut output_read)
13044 .expect("test operation should succeed"),
13045 request
13046 );
13047 }
13048
13049 #[test]
13050 fn protocol_v2_ls_refs_response_rejects_malformed_records() {
13051 assert!(
13052 parse_protocol_v2_ls_refs_response(
13053 ObjectFormat::Sha1,
13054 &[PktLineFrame::Data(
13055 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
13056 )],
13057 )
13058 .is_err()
13059 );
13060 assert!(
13061 parse_protocol_v2_ls_refs_response(
13062 ObjectFormat::Sha1,
13063 &[
13064 PktLineFrame::Data(
13065 b"1111111111111111111111111111111111111111 refs/heads/main peeled:2222222222222222222222222222222222222222 peeled:3333333333333333333333333333333333333333\n"
13066 .to_vec()
13067 ),
13068 PktLineFrame::Flush,
13069 ],
13070 )
13071 .is_err()
13072 );
13073 assert!(
13074 parse_protocol_v2_ls_refs_response(
13075 ObjectFormat::Sha1,
13076 &[
13077 PktLineFrame::Data(
13078 b"unborn HEAD peeled:2222222222222222222222222222222222222222\n".to_vec()
13079 ),
13080 PktLineFrame::Flush,
13081 ],
13082 )
13083 .is_err()
13084 );
13085 assert!(
13086 encode_protocol_v2_ls_refs_response(&[ProtocolV2LsRefsRecord::Ref(
13087 ProtocolV2LsRefsRef {
13088 oid: ObjectId::from_hex(
13089 ObjectFormat::Sha1,
13090 "1111111111111111111111111111111111111111",
13091 )
13092 .expect("test operation should succeed"),
13093 name: "refs/heads/main".into(),
13094 peeled: None,
13095 symref_target: None,
13096 attributes: vec!["peeled:2222222222222222222222222222222222222222".into()],
13097 }
13098 )])
13099 .is_err()
13100 );
13101 }
13102
13103 #[test]
13104 fn protocol_v2_fetch_request_parses_and_encodes_arguments() {
13105 let want = ObjectId::from_hex(
13106 ObjectFormat::Sha1,
13107 "1111111111111111111111111111111111111111",
13108 )
13109 .expect("test operation should succeed");
13110 let have = ObjectId::from_hex(
13111 ObjectFormat::Sha1,
13112 "2222222222222222222222222222222222222222",
13113 )
13114 .expect("test operation should succeed");
13115 let shallow = ObjectId::from_hex(
13116 ObjectFormat::Sha1,
13117 "3333333333333333333333333333333333333333",
13118 )
13119 .expect("test operation should succeed");
13120 let command = ProtocolV2CommandRequest {
13121 command: "fetch".into(),
13122 capabilities: Vec::new(),
13123 arguments: vec![
13124 b"want 1111111111111111111111111111111111111111".to_vec(),
13125 b"want-ref refs/heads/main".to_vec(),
13126 b"have 2222222222222222222222222222222222222222".to_vec(),
13127 b"shallow 3333333333333333333333333333333333333333".to_vec(),
13128 b"deepen 10".to_vec(),
13129 b"deepen-since 123456789".to_vec(),
13130 b"deepen-not refs/tags/v1".to_vec(),
13131 b"deepen-relative".to_vec(),
13132 b"filter blob:none".to_vec(),
13133 b"packfile-uris http,https".to_vec(),
13134 b"thin-pack".to_vec(),
13135 b"no-progress".to_vec(),
13136 b"include-tag".to_vec(),
13137 b"ofs-delta".to_vec(),
13138 b"sideband-all".to_vec(),
13139 b"wait-for-done".to_vec(),
13140 b"done".to_vec(),
13141 ],
13142 };
13143 let request = ProtocolV2FetchRequest::from_command_request(ObjectFormat::Sha1, &command)
13144 .expect("test operation should succeed");
13145 assert_eq!(
13146 request,
13147 ProtocolV2FetchRequest {
13148 wants: vec![want],
13149 want_refs: vec!["refs/heads/main".into()],
13150 haves: vec![have],
13151 shallow: vec![shallow],
13152 deepen: Some(10),
13153 deepen_since: Some(123456789),
13154 deepen_not: vec!["refs/tags/v1".into()],
13155 deepen_relative: true,
13156 filter: Some("blob:none".into()),
13157 packfile_uris: Some("http,https".into()),
13158 thin_pack: true,
13159 no_progress: true,
13160 include_tag: true,
13161 ofs_delta: true,
13162 sideband_all: true,
13163 wait_for_done: true,
13164 done: true,
13165 }
13166 );
13167 assert_eq!(
13168 request
13169 .to_command_request()
13170 .expect("test operation should succeed"),
13171 command
13172 );
13173 }
13174
13175 #[test]
13176 fn protocol_v2_fetch_request_rejects_malformed_arguments() {
13177 assert!(
13178 ProtocolV2FetchRequest::from_command_request(
13179 ObjectFormat::Sha1,
13180 &ProtocolV2CommandRequest {
13181 command: "ls-refs".into(),
13182 capabilities: Vec::new(),
13183 arguments: Vec::new(),
13184 },
13185 )
13186 .is_err()
13187 );
13188 assert!(
13189 ProtocolV2FetchRequest::from_command_request(
13190 ObjectFormat::Sha1,
13191 &ProtocolV2CommandRequest {
13192 command: "fetch".into(),
13193 capabilities: Vec::new(),
13194 arguments: vec![b"want not-an-oid".to_vec()],
13195 },
13196 )
13197 .is_err()
13198 );
13199 assert!(
13200 ProtocolV2FetchRequest::from_command_request(
13201 ObjectFormat::Sha1,
13202 &ProtocolV2CommandRequest {
13203 command: "fetch".into(),
13204 capabilities: Vec::new(),
13205 arguments: vec![b"deepen 0".to_vec()],
13206 },
13207 )
13208 .is_err()
13209 );
13210 assert!(
13211 ProtocolV2FetchRequest::from_command_request(
13212 ObjectFormat::Sha1,
13213 &ProtocolV2CommandRequest {
13214 command: "fetch".into(),
13215 capabilities: Vec::new(),
13216 arguments: vec![b"filter blob:none".to_vec(), b"filter tree:0".to_vec()],
13217 },
13218 )
13219 .is_err()
13220 );
13221 assert!(
13222 ProtocolV2FetchRequest {
13223 deepen: Some(0),
13224 ..ProtocolV2FetchRequest::default()
13225 }
13226 .to_command_request()
13227 .is_err()
13228 );
13229 }
13230
13231 #[test]
13232 fn protocol_v2_fetch_request_streams_round_trip() {
13233 let want = ObjectId::from_hex(
13234 ObjectFormat::Sha1,
13235 "1111111111111111111111111111111111111111",
13236 )
13237 .expect("test operation should succeed");
13238 let have = ObjectId::from_hex(
13239 ObjectFormat::Sha1,
13240 "2222222222222222222222222222222222222222",
13241 )
13242 .expect("test operation should succeed");
13243 let request = ProtocolV2FetchRequest {
13244 wants: vec![want],
13245 haves: vec![have],
13246 deepen: Some(5),
13247 filter: Some("blob:none".into()),
13248 thin_pack: true,
13249 done: true,
13250 ..ProtocolV2FetchRequest::default()
13251 };
13252 let mut encoded = Vec::new();
13253 write_protocol_v2_fetch_request(&mut encoded, &request)
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_request(ObjectFormat::Sha1, &mut input)
13260 .expect("test operation should succeed"),
13261 request
13262 );
13263 assert_eq!(input, b"tail");
13264 }
13265
13266 #[test]
13267 fn protocol_v2_fetch_response_parses_and_encodes_sections() {
13268 let ack = ObjectId::from_hex(
13269 ObjectFormat::Sha1,
13270 "1111111111111111111111111111111111111111",
13271 )
13272 .expect("test operation should succeed");
13273 let shallow = ObjectId::from_hex(
13274 ObjectFormat::Sha1,
13275 "2222222222222222222222222222222222222222",
13276 )
13277 .expect("test operation should succeed");
13278 let wanted = ObjectId::from_hex(
13279 ObjectFormat::Sha1,
13280 "3333333333333333333333333333333333333333",
13281 )
13282 .expect("test operation should succeed");
13283 let pack_hash = ObjectId::from_hex(
13284 ObjectFormat::Sha1,
13285 "4444444444444444444444444444444444444444",
13286 )
13287 .expect("test operation should succeed");
13288 let frames = vec![
13289 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13290 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111\n".to_vec()),
13291 PktLineFrame::Data(b"ready\n".to_vec()),
13292 PktLineFrame::Delimiter,
13293 PktLineFrame::Data(b"shallow-info\n".to_vec()),
13294 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
13295 PktLineFrame::Delimiter,
13296 PktLineFrame::Data(b"wanted-refs\n".to_vec()),
13297 PktLineFrame::Data(
13298 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
13299 ),
13300 PktLineFrame::Delimiter,
13301 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13302 PktLineFrame::Data(
13303 b"4444444444444444444444444444444444444444 https://example.invalid/pack-a.pack\n"
13304 .to_vec(),
13305 ),
13306 PktLineFrame::Delimiter,
13307 PktLineFrame::Data(b"packfile\n".to_vec()),
13308 PktLineFrame::Data(b"\x01PACK bytes".to_vec()),
13309 PktLineFrame::Flush,
13310 ];
13311 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13312 .expect("test operation should succeed");
13313 assert_eq!(
13314 sections,
13315 vec![
13316 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13317 ProtocolV2FetchAcknowledgment::Ack(ack),
13318 ProtocolV2FetchAcknowledgment::Ready,
13319 ]),
13320 ProtocolV2FetchResponseSection::ShallowInfo(vec![
13321 ProtocolV2FetchShallowInfo::Shallow(shallow)
13322 ]),
13323 ProtocolV2FetchResponseSection::WantedRefs(vec![ProtocolV2FetchWantedRef {
13324 oid: wanted,
13325 name: "refs/heads/main".into(),
13326 }]),
13327 ProtocolV2FetchResponseSection::PackfileUris(vec![ProtocolV2FetchPackfileUri {
13328 pack_hash,
13329 uri: "https://example.invalid/pack-a.pack".into(),
13330 }]),
13331 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13332 ]
13333 );
13334 assert_eq!(
13335 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13336 frames
13337 );
13338 }
13339
13340 #[test]
13341 fn protocol_v2_fetch_response_preserves_unknown_sections() {
13342 let frames = vec![
13343 PktLineFrame::Data(b"server-feature\n".to_vec()),
13344 PktLineFrame::Data(b"opaque line\n".to_vec()),
13345 PktLineFrame::Flush,
13346 ];
13347 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13348 .expect("test operation should succeed");
13349 assert_eq!(
13350 sections,
13351 vec![ProtocolV2FetchResponseSection::Unknown {
13352 name: "server-feature".into(),
13353 lines: vec![b"opaque line\n".to_vec()],
13354 }]
13355 );
13356 assert_eq!(
13357 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13358 frames
13359 );
13360 }
13361
13362 #[test]
13363 fn protocol_v2_fetch_response_streams_round_trip() {
13364 let ack = ObjectId::from_hex(
13365 ObjectFormat::Sha1,
13366 "1111111111111111111111111111111111111111",
13367 )
13368 .expect("test operation should succeed");
13369 let sections = vec![
13370 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13371 ProtocolV2FetchAcknowledgment::Ack(ack),
13372 ProtocolV2FetchAcknowledgment::Ready,
13373 ]),
13374 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13375 ];
13376 let mut encoded = Vec::new();
13377 write_protocol_v2_fetch_response(&mut encoded, §ions)
13378 .expect("test operation should succeed");
13379 encoded.extend_from_slice(b"tail");
13380
13381 let mut input = encoded.as_slice();
13382 assert_eq!(
13383 read_protocol_v2_fetch_response(ObjectFormat::Sha1, &mut input)
13384 .expect("test operation should succeed"),
13385 sections
13386 );
13387 assert_eq!(input, b"tail");
13388 }
13389
13390 #[test]
13391 fn protocol_v2_fetch_sideband_all_response_parses_sections_and_progress() {
13392 let frames = vec![
13393 PktLineFrame::Data(
13394 encode_sideband_packet(&SideBandPacket {
13395 channel: SideBandChannel::Data,
13396 data: b"acknowledgments\n".to_vec(),
13397 })
13398 .expect("test operation should succeed"),
13399 ),
13400 PktLineFrame::Data(
13401 encode_sideband_packet(&SideBandPacket {
13402 channel: SideBandChannel::Data,
13403 data: b"NAK\n".to_vec(),
13404 })
13405 .expect("test operation should succeed"),
13406 ),
13407 PktLineFrame::Data(
13408 encode_sideband_packet(&SideBandPacket {
13409 channel: SideBandChannel::Progress,
13410 data: b"keepalive\n".to_vec(),
13411 })
13412 .expect("test operation should succeed"),
13413 ),
13414 PktLineFrame::Delimiter,
13415 PktLineFrame::Data(
13416 encode_sideband_packet(&SideBandPacket {
13417 channel: SideBandChannel::Data,
13418 data: b"packfile\n".to_vec(),
13419 })
13420 .expect("test operation should succeed"),
13421 ),
13422 PktLineFrame::Data(b"\x01PACK".to_vec()),
13423 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
13424 PktLineFrame::Flush,
13425 ];
13426
13427 let response = parse_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &frames)
13428 .expect("test operation should succeed");
13429 assert_eq!(
13430 response,
13431 ProtocolV2FetchSidebandAllResponse {
13432 sections: vec![
13433 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13434 ProtocolV2FetchAcknowledgment::Nak
13435 ]),
13436 ProtocolV2FetchResponseSection::Packfile(vec![
13437 b"\x01PACK".to_vec(),
13438 b"\x02counting objects\n".to_vec(),
13439 ]),
13440 ],
13441 progress: vec![b"keepalive\n".to_vec()],
13442 }
13443 );
13444 assert_eq!(
13445 demux_protocol_v2_fetch_packfile(&response.sections)
13446 .expect("test operation should succeed"),
13447 Some(SideBandDemux {
13448 data: b"PACK".to_vec(),
13449 progress: vec![b"counting objects\n".to_vec()],
13450 })
13451 );
13452 }
13453
13454 #[test]
13455 fn protocol_v2_fetch_sideband_all_response_streams_round_trip() {
13456 let sections = vec![
13457 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13458 ProtocolV2FetchAcknowledgment::Nak,
13459 ]),
13460 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13461 ];
13462 let mut encoded = Vec::new();
13463 write_protocol_v2_fetch_sideband_all_response(&mut encoded, §ions)
13464 .expect("test operation should succeed");
13465 encoded.extend_from_slice(b"tail");
13466
13467 let mut input = encoded.as_slice();
13468 assert_eq!(
13469 read_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &mut input)
13470 .expect("test operation should succeed"),
13471 ProtocolV2FetchSidebandAllResponse {
13472 sections: sections.clone(),
13473 progress: Vec::new(),
13474 }
13475 );
13476 assert_eq!(input, b"tail");
13477
13478 let mut encoded = Vec::new();
13479 write_protocol_v2_fetch_sideband_all_response_with_response_end(&mut encoded, §ions)
13480 .expect("test operation should succeed");
13481 encoded.extend_from_slice(b"tail");
13482
13483 let mut input = encoded.as_slice();
13484 assert_eq!(
13485 read_protocol_v2_fetch_sideband_all_response_until_response_end(
13486 ObjectFormat::Sha1,
13487 &mut input,
13488 )
13489 .expect("test operation should succeed")
13490 .sections,
13491 sections
13492 );
13493 assert_eq!(input, b"tail");
13494 }
13495
13496 #[test]
13497 fn protocol_v2_fetch_sideband_all_response_rejects_malformed_sideband() {
13498 assert!(
13499 parse_protocol_v2_fetch_sideband_all_response(
13500 ObjectFormat::Sha1,
13501 &[
13502 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13503 PktLineFrame::Flush,
13504 ],
13505 )
13506 .is_err()
13507 );
13508 assert!(
13509 parse_protocol_v2_fetch_sideband_all_response(
13510 ObjectFormat::Sha1,
13511 &[
13512 PktLineFrame::Data(
13513 encode_sideband_packet(&SideBandPacket {
13514 channel: SideBandChannel::Fatal,
13515 data: b"remote died\n".to_vec(),
13516 })
13517 .expect("test operation should succeed"),
13518 ),
13519 PktLineFrame::Flush,
13520 ],
13521 )
13522 .is_err()
13523 );
13524 }
13525
13526 #[test]
13527 fn protocol_v2_object_info_response_parses_and_encodes_size_records() {
13528 let oid = ObjectId::from_hex(
13529 ObjectFormat::Sha1,
13530 "1111111111111111111111111111111111111111",
13531 )
13532 .expect("test operation should succeed");
13533 let frames = vec![
13534 PktLineFrame::Data(b"size\n".to_vec()),
13535 PktLineFrame::Data(b"1111111111111111111111111111111111111111 12345\n".to_vec()),
13536 PktLineFrame::Flush,
13537 ];
13538 let response = parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &frames)
13539 .expect("test operation should succeed");
13540 assert_eq!(
13541 response,
13542 ProtocolV2ObjectInfoResponse {
13543 size: true,
13544 records: vec![ProtocolV2ObjectInfoRecord { oid, size: 12345 }],
13545 }
13546 );
13547 assert_eq!(
13548 encode_protocol_v2_object_info_response(&response)
13549 .expect("test operation should succeed"),
13550 frames
13551 );
13552 }
13553
13554 #[test]
13555 fn protocol_v2_object_info_response_streams_and_exchanges() {
13556 let request = ProtocolV2ObjectInfoRequest {
13557 size: true,
13558 oids: vec![
13559 ObjectId::from_hex(
13560 ObjectFormat::Sha1,
13561 "1111111111111111111111111111111111111111",
13562 )
13563 .expect("test operation should succeed"),
13564 ],
13565 };
13566 let response = ProtocolV2ObjectInfoResponse {
13567 size: true,
13568 records: vec![ProtocolV2ObjectInfoRecord {
13569 oid: request.oids[0].clone(),
13570 size: 7,
13571 }],
13572 };
13573
13574 let mut encoded = Vec::new();
13575 write_protocol_v2_object_info_response(&mut encoded, &response)
13576 .expect("test operation should succeed");
13577 encoded.extend_from_slice(b"tail");
13578 let mut input = encoded.as_slice();
13579 assert_eq!(
13580 read_protocol_v2_object_info_response(ObjectFormat::Sha1, &mut input)
13581 .expect("test operation should succeed"),
13582 response
13583 );
13584 assert_eq!(input, b"tail");
13585
13586 let mut response_bytes = Vec::new();
13587 write_protocol_v2_object_info_response(&mut response_bytes, &response)
13588 .expect("test operation should succeed");
13589 let mut input = response_bytes.as_slice();
13590 let mut output = Vec::new();
13591 assert_eq!(
13592 exchange_protocol_v2_object_info(
13593 ObjectFormat::Sha1,
13594 &mut input,
13595 &mut output,
13596 &request,
13597 )
13598 .expect("test operation should succeed"),
13599 response
13600 );
13601 assert!(input.is_empty());
13602 let mut output_read = output.as_slice();
13603 assert_eq!(
13604 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut output_read)
13605 .expect("test operation should succeed"),
13606 request
13607 );
13608 }
13609
13610 #[test]
13611 fn protocol_v2_object_info_response_rejects_malformed_records() {
13612 assert!(parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &[]).is_err());
13613 assert!(
13614 parse_protocol_v2_object_info_response(
13615 ObjectFormat::Sha1,
13616 &[PktLineFrame::Data(b"size\n".to_vec())],
13617 )
13618 .is_err()
13619 );
13620 assert!(
13621 parse_protocol_v2_object_info_response(
13622 ObjectFormat::Sha1,
13623 &[PktLineFrame::Data(b"type\n".to_vec()), PktLineFrame::Flush,],
13624 )
13625 .is_err()
13626 );
13627 assert!(
13628 parse_protocol_v2_object_info_response(
13629 ObjectFormat::Sha1,
13630 &[
13631 PktLineFrame::Data(b"size\n".to_vec()),
13632 PktLineFrame::Data(
13633 b"1111111111111111111111111111111111111111 not-a-size\n".to_vec()
13634 ),
13635 PktLineFrame::Flush,
13636 ],
13637 )
13638 .is_err()
13639 );
13640 assert!(
13641 parse_protocol_v2_object_info_response(
13642 ObjectFormat::Sha1,
13643 &[
13644 PktLineFrame::Data(b"size\n".to_vec()),
13645 PktLineFrame::Delimiter,
13646 PktLineFrame::Flush,
13647 ],
13648 )
13649 .is_err()
13650 );
13651 assert!(
13652 encode_protocol_v2_object_info_response(&ProtocolV2ObjectInfoResponse {
13653 size: false,
13654 records: Vec::new(),
13655 })
13656 .is_err()
13657 );
13658 }
13659
13660 #[test]
13661 fn protocol_v2_fetch_response_reads_stateless_response_end() {
13662 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13663 ProtocolV2FetchAcknowledgment::Nak,
13664 ])];
13665 let mut encoded = Vec::new();
13666 write_protocol_v2_fetch_response_with_response_end(&mut encoded, §ions)
13667 .expect("test operation should succeed");
13668 encoded.extend_from_slice(b"tail");
13669
13670 let mut input = encoded.as_slice();
13671 assert_eq!(
13672 read_protocol_v2_fetch_response_until_response_end(ObjectFormat::Sha1, &mut input)
13673 .expect("test operation should succeed"),
13674 sections
13675 );
13676 assert_eq!(input, b"tail");
13677 assert!(
13678 parse_protocol_v2_fetch_response(
13679 ObjectFormat::Sha1,
13680 &[
13681 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13682 PktLineFrame::ResponseEnd,
13683 ],
13684 )
13685 .is_err()
13686 );
13687 }
13688
13689 #[test]
13690 fn protocol_v2_fetch_exchange_writes_request_and_reads_response() {
13691 let want = ObjectId::from_hex(
13692 ObjectFormat::Sha1,
13693 "1111111111111111111111111111111111111111",
13694 )
13695 .expect("test operation should succeed");
13696 let request = ProtocolV2FetchRequest {
13697 wants: vec![want],
13698 thin_pack: true,
13699 done: true,
13700 ..ProtocolV2FetchRequest::default()
13701 };
13702 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13703 ProtocolV2FetchAcknowledgment::Nak,
13704 ])];
13705 let mut response = Vec::new();
13706 write_protocol_v2_fetch_response(&mut response, §ions)
13707 .expect("test operation should succeed");
13708
13709 let mut input = response.as_slice();
13710 let mut output = Vec::new();
13711 assert_eq!(
13712 exchange_protocol_v2_fetch(ObjectFormat::Sha1, &mut input, &mut output, &request)
13713 .expect("test operation should succeed"),
13714 sections
13715 );
13716 assert!(input.is_empty());
13717 let mut output_read = output.as_slice();
13718 assert_eq!(
13719 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut output_read)
13720 .expect("test operation should succeed"),
13721 request
13722 );
13723 }
13724
13725 #[test]
13726 fn protocol_v2_fetch_packfile_demuxes_sideband_section() {
13727 let sections = vec![
13728 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13729 ProtocolV2FetchAcknowledgment::Nak,
13730 ]),
13731 ProtocolV2FetchResponseSection::Packfile(vec![
13732 b"\x01PACK".to_vec(),
13733 b"\x02counting objects\n".to_vec(),
13734 b"\x01 bytes".to_vec(),
13735 b"\x02done\n".to_vec(),
13736 ]),
13737 ];
13738
13739 assert_eq!(
13740 demux_protocol_v2_fetch_packfile(§ions).expect("test operation should succeed"),
13741 Some(SideBandDemux {
13742 data: b"PACK bytes".to_vec(),
13743 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
13744 })
13745 );
13746 assert_eq!(
13747 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Acknowledgments(
13748 vec![ProtocolV2FetchAcknowledgment::Nak],
13749 )])
13750 .expect("test operation should succeed"),
13751 None
13752 );
13753 }
13754
13755 #[test]
13756 fn protocol_v2_fetch_packfile_demux_rejects_duplicate_or_bad_sideband() {
13757 assert!(
13758 demux_protocol_v2_fetch_packfile(&[
13759 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK".to_vec()]),
13760 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01more".to_vec()]),
13761 ])
13762 .is_err()
13763 );
13764 assert!(
13765 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13766 b"\x03remote died\n".to_vec()
13767 ])])
13768 .is_err()
13769 );
13770 assert!(
13771 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13772 b"\x04bad".to_vec()
13773 ])])
13774 .is_err()
13775 );
13776 }
13777
13778 #[test]
13779 fn protocol_v2_fetch_response_rejects_malformed_sections() {
13780 assert!(
13781 parse_protocol_v2_fetch_response(
13782 ObjectFormat::Sha1,
13783 &[PktLineFrame::Data(b"acknowledgments\n".to_vec())],
13784 )
13785 .is_err()
13786 );
13787 assert!(
13788 parse_protocol_v2_fetch_response(
13789 ObjectFormat::Sha1,
13790 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
13791 )
13792 .is_err()
13793 );
13794 assert!(
13795 parse_protocol_v2_fetch_response(
13796 ObjectFormat::Sha1,
13797 &[
13798 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13799 PktLineFrame::Data(b"ACK not-an-oid\n".to_vec()),
13800 PktLineFrame::Flush,
13801 ],
13802 )
13803 .is_err()
13804 );
13805 assert!(
13806 parse_protocol_v2_fetch_response(
13807 ObjectFormat::Sha1,
13808 &[
13809 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13810 PktLineFrame::Data(b"https://example.invalid/pack-a.pack\n".to_vec()),
13811 PktLineFrame::Flush,
13812 ],
13813 )
13814 .is_err()
13815 );
13816 assert!(
13817 parse_protocol_v2_fetch_response(
13818 ObjectFormat::Sha1,
13819 &[
13820 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13821 PktLineFrame::Data(
13822 b"not-a-hash https://example.invalid/pack-a.pack\n".to_vec()
13823 ),
13824 PktLineFrame::Flush,
13825 ],
13826 )
13827 .is_err()
13828 );
13829 assert!(
13830 encode_protocol_v2_fetch_response(&[ProtocolV2FetchResponseSection::WantedRefs(vec![
13831 ProtocolV2FetchWantedRef {
13832 oid: ObjectId::from_hex(
13833 ObjectFormat::Sha1,
13834 "1111111111111111111111111111111111111111",
13835 )
13836 .expect("test operation should succeed"),
13837 name: "bad ref".into(),
13838 }
13839 ])])
13840 .is_err()
13841 );
13842 }
13843
13844 #[test]
13845 fn protocol_v2_ls_refs_response_bridges_into_ref_advertisement_set() {
13846 let head = ObjectId::from_hex(
13847 ObjectFormat::Sha1,
13848 "1111111111111111111111111111111111111111",
13849 )
13850 .expect("test operation should succeed");
13851 let tag = ObjectId::from_hex(
13852 ObjectFormat::Sha1,
13853 "2222222222222222222222222222222222222222",
13854 )
13855 .expect("test operation should succeed");
13856 let tag_peeled = ObjectId::from_hex(
13857 ObjectFormat::Sha1,
13858 "3333333333333333333333333333333333333333",
13859 )
13860 .expect("test operation should succeed");
13861 let frames = vec![
13862 PktLineFrame::Data(
13863 b"1111111111111111111111111111111111111111 HEAD symref-target:refs/heads/main\n"
13864 .to_vec(),
13865 ),
13866 PktLineFrame::Data(
13867 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
13868 ),
13869 PktLineFrame::Data(
13870 b"2222222222222222222222222222222222222222 refs/tags/v1 peeled:3333333333333333333333333333333333333333\n"
13871 .to_vec(),
13872 ),
13873 PktLineFrame::Flush,
13874 ];
13875
13876 let set = parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13877 ObjectFormat::Sha1,
13878 &frames,
13879 )
13880 .expect("test operation should succeed");
13881 assert_eq!(
13882 set,
13883 RefAdvertisementSet {
13884 protocol: ProtocolVersion::V2,
13885 refs: vec![
13886 RefAdvertisement {
13887 oid: head.clone(),
13888 name: "HEAD".into(),
13889 capabilities: vec![Capability {
13890 name: "symref".into(),
13891 value: Some("HEAD:refs/heads/main".into()),
13892 }],
13893 },
13894 RefAdvertisement {
13895 oid: head,
13896 name: "refs/heads/main".into(),
13897 capabilities: Vec::new(),
13898 },
13899 RefAdvertisement {
13900 oid: tag,
13901 name: "refs/tags/v1".into(),
13902 capabilities: Vec::new(),
13903 },
13904 RefAdvertisement {
13905 oid: tag_peeled,
13906 name: "refs/tags/v1^{}".into(),
13907 capabilities: Vec::new(),
13908 },
13909 ],
13910 shallow: Vec::new(),
13911 }
13912 );
13913
13914 let mut encoded = Vec::new();
13916 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
13917 encoded.extend_from_slice(b"tail");
13918 let mut input = encoded.as_slice();
13919 assert_eq!(
13920 read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13921 ObjectFormat::Sha1,
13922 &mut input,
13923 )
13924 .expect("test operation should succeed"),
13925 set,
13926 );
13927 assert_eq!(input, b"tail");
13928 }
13929
13930 #[test]
13931 fn protocol_v2_ls_refs_records_bridge_unborn_head_symref_and_empty() {
13932 let records = vec![ProtocolV2LsRefsRecord::Unborn {
13935 name: "HEAD".into(),
13936 symref_target: Some("refs/heads/main".into()),
13937 attributes: Vec::new(),
13938 }];
13939 assert!(protocol_v2_ls_refs_records_to_ref_advertisement_set(&records).is_err());
13940
13941 assert_eq!(
13943 protocol_v2_ls_refs_records_to_ref_advertisement_set(&[])
13944 .expect("test operation should succeed"),
13945 RefAdvertisementSet {
13946 protocol: ProtocolVersion::V2,
13947 refs: Vec::new(),
13948 shallow: Vec::new(),
13949 }
13950 );
13951
13952 let main = ObjectId::from_hex(
13955 ObjectFormat::Sha1,
13956 "4444444444444444444444444444444444444444",
13957 )
13958 .expect("test operation should succeed");
13959 let records = vec![
13960 ProtocolV2LsRefsRecord::Unborn {
13961 name: "HEAD".into(),
13962 symref_target: Some("refs/heads/main".into()),
13963 attributes: Vec::new(),
13964 },
13965 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13966 oid: main.clone(),
13967 name: "refs/heads/main".into(),
13968 peeled: None,
13969 symref_target: None,
13970 attributes: Vec::new(),
13971 }),
13972 ];
13973 let set = protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
13974 .expect("test operation should succeed");
13975 assert_eq!(
13976 set,
13977 RefAdvertisementSet {
13978 protocol: ProtocolVersion::V2,
13979 refs: vec![RefAdvertisement {
13980 oid: main,
13981 name: "refs/heads/main".into(),
13982 capabilities: vec![Capability {
13983 name: "symref".into(),
13984 value: Some("HEAD:refs/heads/main".into()),
13985 }],
13986 }],
13987 shallow: Vec::new(),
13988 }
13989 );
13990 }
13991}