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 header = build_receive_pack_push_request_header(features, commands, options)?;
1001 let request = ReceivePackPushRequest {
1002 commands: header.commands,
1003 push_options: header.push_options,
1004 packfile,
1005 };
1006 validate_receive_pack_push_request_features(features, &request)?;
1007 Ok(request)
1008}
1009
1010pub fn build_receive_pack_push_request_header(
1011 features: &ReceivePackFeatures,
1012 commands: Vec<ReceivePackCommand>,
1013 options: ReceivePackPushRequestOptions,
1014) -> Result<ReceivePackPushRequestHeader> {
1015 let mut capabilities = Vec::new();
1016 if options.report_status_v2 {
1017 require_receive_pack_feature(features.report_status_v2, "report-status-v2")?;
1018 capabilities.push(Capability {
1019 name: "report-status-v2".into(),
1020 value: None,
1021 });
1022 } else if options.report_status {
1023 require_receive_pack_feature(features.report_status, "report-status")?;
1024 capabilities.push(Capability {
1025 name: "report-status".into(),
1026 value: None,
1027 });
1028 }
1029 if commands.iter().any(is_receive_pack_delete_command) {
1030 require_receive_pack_feature(features.delete_refs, "delete-refs")?;
1031 capabilities.push(Capability {
1032 name: "delete-refs".into(),
1033 value: None,
1034 });
1035 }
1036 if options.atomic {
1037 require_receive_pack_feature(features.atomic, "atomic")?;
1038 capabilities.push(Capability {
1039 name: "atomic".into(),
1040 value: None,
1041 });
1042 }
1043 if options.ofs_delta {
1044 require_receive_pack_feature(features.ofs_delta, "ofs-delta")?;
1045 capabilities.push(Capability {
1046 name: "ofs-delta".into(),
1047 value: None,
1048 });
1049 }
1050 if options.side_band_64k {
1051 require_receive_pack_feature(features.side_band_64k, "side-band-64k")?;
1052 capabilities.push(Capability {
1053 name: "side-band-64k".into(),
1054 value: None,
1055 });
1056 }
1057 if options.quiet {
1058 require_receive_pack_feature(features.quiet, "quiet")?;
1059 capabilities.push(Capability {
1060 name: "quiet".into(),
1061 value: None,
1062 });
1063 }
1064 if let Some(agent) = &options.agent {
1065 validate_capability_field("receive-pack request agent", agent)?;
1066 capabilities.push(Capability {
1067 name: "agent".into(),
1068 value: Some(agent.clone()),
1069 });
1070 }
1071 if let Some(format) = options.object_format {
1072 if features.object_format != Some(format) {
1073 return Err(GitError::InvalidFormat(
1074 "receive-pack request object-format was not advertised".into(),
1075 ));
1076 }
1077 capabilities.push(Capability {
1078 name: "object-format".into(),
1079 value: Some(format.name().into()),
1080 });
1081 }
1082 let push_options = if options.push_options.is_empty() {
1083 None
1084 } else {
1085 require_receive_pack_feature(features.push_options, "push-options")?;
1086 for option in &options.push_options {
1087 validate_receive_pack_push_option(option.as_bytes())?;
1088 }
1089 capabilities.push(Capability {
1090 name: "push-options".into(),
1091 value: None,
1092 });
1093 Some(options.push_options)
1094 };
1095 let header = ReceivePackPushRequestHeader {
1096 commands: ReceivePackRequest {
1097 commands,
1098 capabilities,
1099 shallow: Vec::new(),
1100 },
1101 push_options,
1102 };
1103 validate_receive_pack_push_request_features(
1104 features,
1105 &ReceivePackPushRequest {
1106 commands: header.commands.clone(),
1107 push_options: header.push_options.clone(),
1108 packfile: Vec::new(),
1109 },
1110 )?;
1111 Ok(header)
1112}
1113
1114pub fn smart_http_info_refs_path(repository_path: &str, service: GitService) -> Result<String> {
1115 validate_smart_http_service(service)?;
1116 let repository_path = normalize_http_repository_path(repository_path)?;
1117 Ok(format!(
1118 "{repository_path}/info/refs?service={}",
1119 service.as_str()
1120 ))
1121}
1122
1123pub fn smart_http_rpc_path(repository_path: &str, service: GitService) -> Result<String> {
1124 validate_smart_http_service(service)?;
1125 let repository_path = normalize_http_repository_path(repository_path)?;
1126 Ok(format!("{repository_path}/{}", service.as_str()))
1127}
1128
1129pub fn dumb_http_info_refs_path(repository_path: &str) -> Result<String> {
1130 let repository_path = normalize_http_repository_path(repository_path)?;
1131 Ok(format!("{repository_path}/info/refs"))
1132}
1133
1134pub fn dumb_http_alternates_path(repository_path: &str) -> Result<String> {
1135 let repository_path = normalize_http_repository_path(repository_path)?;
1136 Ok(format!("{repository_path}/objects/info/http-alternates"))
1137}
1138
1139pub fn dumb_http_packs_path(repository_path: &str) -> Result<String> {
1140 let repository_path = normalize_http_repository_path(repository_path)?;
1141 Ok(format!("{repository_path}/objects/info/packs"))
1142}
1143
1144pub fn dumb_http_loose_object_path(repository_path: &str, oid: &ObjectId) -> Result<String> {
1145 let repository_path = normalize_http_repository_path(repository_path)?;
1146 let oid = oid.to_string();
1147 let (directory, file) = oid.split_at(2);
1148 Ok(format!("{repository_path}/objects/{directory}/{file}"))
1149}
1150
1151pub fn dumb_http_pack_file_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
1152 dumb_http_pack_resource_path(repository_path, hash, "pack")
1153}
1154
1155pub fn dumb_http_pack_index_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
1156 dumb_http_pack_resource_path(repository_path, hash, "idx")
1157}
1158
1159pub fn smart_http_advertisement_content_type(service: GitService) -> Result<String> {
1160 validate_smart_http_service(service)?;
1161 Ok(format!("application/x-{}-advertisement", service.as_str()))
1162}
1163
1164pub fn smart_http_rpc_request_content_type(service: GitService) -> Result<String> {
1165 validate_smart_http_service(service)?;
1166 Ok(format!("application/x-{}-request", service.as_str()))
1167}
1168
1169pub fn smart_http_rpc_result_content_type(service: GitService) -> Result<String> {
1170 validate_smart_http_service(service)?;
1171 Ok(format!("application/x-{}-result", service.as_str()))
1172}
1173
1174pub fn parse_smart_http_advertisement_content_type(value: &str) -> Result<GitService> {
1175 parse_smart_http_content_type(value, "-advertisement")
1176}
1177
1178pub fn parse_smart_http_rpc_request_content_type(value: &str) -> Result<GitService> {
1179 parse_smart_http_content_type(value, "-request")
1180}
1181
1182pub fn parse_smart_http_rpc_result_content_type(value: &str) -> Result<GitService> {
1183 parse_smart_http_content_type(value, "-result")
1184}
1185
1186pub fn parse_sideband_packet(payload: &[u8]) -> Result<SideBandPacket> {
1187 let Some((&channel, data)) = payload.split_first() else {
1188 return Err(GitError::InvalidFormat("sideband packet is empty".into()));
1189 };
1190 let channel = match channel {
1191 1 => SideBandChannel::Data,
1192 2 => SideBandChannel::Progress,
1193 3 => SideBandChannel::Fatal,
1194 other => {
1195 return Err(GitError::InvalidFormat(format!(
1196 "invalid sideband channel {other}"
1197 )));
1198 }
1199 };
1200 Ok(SideBandPacket {
1201 channel,
1202 data: data.to_vec(),
1203 })
1204}
1205
1206pub fn encode_sideband_packet(packet: &SideBandPacket) -> Result<Vec<u8>> {
1207 let mut out = Vec::with_capacity(packet.data.len() + 1);
1208 out.push(match packet.channel {
1209 SideBandChannel::Data => 1,
1210 SideBandChannel::Progress => 2,
1211 SideBandChannel::Fatal => 3,
1212 });
1213 out.extend_from_slice(&packet.data);
1214 if out.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 Ok(out)
1220}
1221
1222pub fn write_sideband_packet(writer: &mut impl Write, packet: &SideBandPacket) -> Result<()> {
1223 write_sideband_payload(writer, packet.channel, &packet.data)
1224}
1225
1226fn write_sideband_payload(
1227 writer: &mut impl Write,
1228 channel: SideBandChannel,
1229 data: &[u8],
1230) -> Result<()> {
1231 let payload_len = data
1232 .len()
1233 .checked_add(1)
1234 .ok_or_else(|| GitError::InvalidFormat("sideband packet length overflow".into()))?;
1235 if payload_len > PKT_LINE_MAX_PAYLOAD_LEN {
1236 return Err(GitError::InvalidFormat(format!(
1237 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1238 )));
1239 }
1240 writer.write_all(&pkt_line_header(payload_len + 4))?;
1241 writer.write_all(&[match channel {
1242 SideBandChannel::Data => 1,
1243 SideBandChannel::Progress => 2,
1244 SideBandChannel::Fatal => 3,
1245 }])?;
1246 writer.write_all(data)?;
1247 Ok(())
1248}
1249
1250pub fn parse_sideband_packets(payloads: &[Vec<u8>]) -> Result<Vec<SideBandPacket>> {
1251 payloads
1252 .iter()
1253 .map(|payload| parse_sideband_packet(payload))
1254 .collect()
1255}
1256
1257pub fn encode_sideband_packets(packets: &[SideBandPacket]) -> Result<Vec<Vec<u8>>> {
1258 packets.iter().map(encode_sideband_packet).collect()
1259}
1260
1261pub fn parse_sideband_stream(frames: &[PktLineFrame]) -> Result<Vec<SideBandPacket>> {
1262 let mut packets = Vec::new();
1263 let mut saw_flush = false;
1264 for (idx, frame) in frames.iter().enumerate() {
1265 match frame {
1266 PktLineFrame::Data(payload) if !saw_flush => {
1267 packets.push(parse_sideband_packet(payload)?);
1268 }
1269 PktLineFrame::Data(_) => {
1270 return Err(GitError::InvalidFormat(
1271 "sideband stream has data after flush".into(),
1272 ));
1273 }
1274 PktLineFrame::Flush => {
1275 saw_flush = true;
1276 if idx + 1 != frames.len() {
1277 return Err(GitError::InvalidFormat(
1278 "sideband stream has frames after flush".into(),
1279 ));
1280 }
1281 }
1282 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1283 return Err(GitError::InvalidFormat(
1284 "sideband stream contains a non-flush control packet".into(),
1285 ));
1286 }
1287 }
1288 }
1289 if !saw_flush {
1290 return Err(GitError::InvalidFormat(
1291 "sideband stream missing flush".into(),
1292 ));
1293 }
1294 Ok(packets)
1295}
1296
1297pub fn encode_sideband_stream(packets: &[SideBandPacket]) -> Result<Vec<PktLineFrame>> {
1298 let mut frames = Vec::new();
1299 for packet in packets {
1300 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
1301 }
1302 frames.push(PktLineFrame::Flush);
1303 Ok(frames)
1304}
1305
1306pub fn read_sideband_stream(reader: &mut impl Read) -> Result<Vec<SideBandPacket>> {
1307 let frames = read_pkt_line_frames_until_flush(reader)?;
1308 parse_sideband_stream(&frames)
1309}
1310
1311pub fn write_sideband_stream(writer: &mut impl Write, packets: &[SideBandPacket]) -> Result<()> {
1312 for packet in packets {
1313 write_sideband_packet(writer, packet)?;
1314 }
1315 writer.write_all(b"0000")?;
1316 Ok(())
1317}
1318
1319pub fn demux_sideband_packets(packets: &[SideBandPacket]) -> Result<SideBandDemux> {
1320 let mut out = SideBandDemux::default();
1321 for packet in packets {
1322 match packet.channel {
1323 SideBandChannel::Data => out.data.extend_from_slice(&packet.data),
1324 SideBandChannel::Progress => out.progress.push(packet.data.clone()),
1325 SideBandChannel::Fatal => {
1326 let message = String::from_utf8_lossy(&packet.data).into_owned();
1327 return Err(GitError::InvalidFormat(format!(
1328 "sideband fatal: {message}"
1329 )));
1330 }
1331 }
1332 }
1333 Ok(out)
1334}
1335
1336pub fn parse_and_demux_sideband_packets(payloads: &[Vec<u8>]) -> Result<SideBandDemux> {
1337 let packets = parse_sideband_packets(payloads)?;
1338 demux_sideband_packets(&packets)
1339}
1340
1341pub fn demux_sideband_stream(frames: &[PktLineFrame]) -> Result<SideBandDemux> {
1342 let packets = parse_sideband_stream(frames)?;
1343 demux_sideband_packets(&packets)
1344}
1345
1346pub fn read_and_demux_sideband_stream(reader: &mut impl Read) -> Result<SideBandDemux> {
1347 let packets = read_sideband_stream(reader)?;
1348 demux_sideband_packets(&packets)
1349}
1350
1351pub fn parse_upload_archive_request(frames: &[PktLineFrame]) -> Result<UploadArchiveRequest> {
1352 let mut request = UploadArchiveRequest::default();
1353 let mut saw_flush = false;
1354 for (idx, frame) in frames.iter().enumerate() {
1355 match frame {
1356 PktLineFrame::Data(payload) if !saw_flush => {
1357 let text = parse_protocol_v2_line_text("upload-archive request argument", payload)?;
1358 let argument = text.strip_prefix("argument ").ok_or_else(|| {
1359 GitError::InvalidFormat("upload-archive request line must be argument".into())
1360 })?;
1361 validate_upload_archive_argument(argument)?;
1362 request.arguments.push(argument.to_string());
1363 }
1364 PktLineFrame::Data(_) => {
1365 return Err(GitError::InvalidFormat(
1366 "upload-archive request has data after flush".into(),
1367 ));
1368 }
1369 PktLineFrame::Flush => {
1370 saw_flush = true;
1371 if idx + 1 != frames.len() {
1372 return Err(GitError::InvalidFormat(
1373 "upload-archive request has frames after flush".into(),
1374 ));
1375 }
1376 }
1377 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1378 return Err(GitError::InvalidFormat(
1379 "upload-archive request contains a non-flush control packet".into(),
1380 ));
1381 }
1382 }
1383 }
1384 if !saw_flush {
1385 return Err(GitError::InvalidFormat(
1386 "upload-archive request missing flush".into(),
1387 ));
1388 }
1389 if request.arguments.is_empty() {
1390 return Err(GitError::InvalidFormat(
1391 "upload-archive request is missing arguments".into(),
1392 ));
1393 }
1394 Ok(request)
1395}
1396
1397pub fn encode_upload_archive_request(request: &UploadArchiveRequest) -> Result<Vec<PktLineFrame>> {
1398 if request.arguments.is_empty() {
1399 return Err(GitError::InvalidFormat(
1400 "upload-archive request is missing arguments".into(),
1401 ));
1402 }
1403 let mut frames = Vec::new();
1404 for argument in &request.arguments {
1405 validate_upload_archive_argument(argument)?;
1406 frames.push(PktLineFrame::data(line_from_str(&format!(
1407 "argument {argument}"
1408 )))?);
1409 }
1410 frames.push(PktLineFrame::Flush);
1411 Ok(frames)
1412}
1413
1414pub fn read_upload_archive_request(reader: &mut impl Read) -> Result<UploadArchiveRequest> {
1415 let frames = read_pkt_line_frames_until_flush(reader)?;
1416 parse_upload_archive_request(&frames)
1417}
1418
1419pub fn write_upload_archive_request(
1420 writer: &mut impl Write,
1421 request: &UploadArchiveRequest,
1422) -> Result<()> {
1423 if request.arguments.is_empty() {
1424 return Err(GitError::InvalidFormat(
1425 "upload-archive request is missing arguments".into(),
1426 ));
1427 }
1428 for argument in &request.arguments {
1429 validate_upload_archive_argument(argument)?;
1430 write_pkt_line_payload(writer, &line_from_str(&format!("argument {argument}")))?;
1431 }
1432 writer.write_all(b"0000")?;
1433 Ok(())
1434}
1435
1436pub fn parse_upload_archive_response(frames: &[PktLineFrame]) -> Result<UploadArchiveResponse> {
1437 let Some((first, rest)) = frames.split_first() else {
1438 return Err(GitError::InvalidFormat(
1439 "upload-archive response is empty".into(),
1440 ));
1441 };
1442 let PktLineFrame::Data(payload) = first else {
1443 return Err(GitError::InvalidFormat(
1444 "upload-archive response must start with a data packet".into(),
1445 ));
1446 };
1447 let text = parse_protocol_v2_line_text("upload-archive response status", payload)?;
1448 if text == "ACK" {
1449 return Ok(UploadArchiveResponse::Ack {
1450 sideband: parse_sideband_stream(rest)?,
1451 });
1452 }
1453 if let Some(message) = text.strip_prefix("NACK ") {
1454 validate_upload_archive_status_message(message)?;
1455 if !matches!(rest, [PktLineFrame::Flush]) {
1456 return Err(GitError::InvalidFormat(
1457 "upload-archive NACK response must end with flush".into(),
1458 ));
1459 }
1460 return Ok(UploadArchiveResponse::Nack {
1461 message: message.to_string(),
1462 });
1463 }
1464 Err(GitError::InvalidFormat(format!(
1465 "unsupported upload-archive response status {text}"
1466 )))
1467}
1468
1469pub fn encode_upload_archive_response(
1470 response: &UploadArchiveResponse,
1471) -> Result<Vec<PktLineFrame>> {
1472 let mut frames = Vec::new();
1473 match response {
1474 UploadArchiveResponse::Ack { sideband } => {
1475 frames.push(PktLineFrame::data(line_from_str("ACK"))?);
1476 frames.extend(encode_sideband_stream(sideband)?);
1477 }
1478 UploadArchiveResponse::Nack { message } => {
1479 validate_upload_archive_status_message(message)?;
1480 frames.push(PktLineFrame::data(line_from_str(&format!(
1481 "NACK {message}"
1482 )))?);
1483 frames.push(PktLineFrame::Flush);
1484 }
1485 }
1486 Ok(frames)
1487}
1488
1489pub fn read_upload_archive_response(reader: &mut impl Read) -> Result<UploadArchiveResponse> {
1490 let frames = read_pkt_line_frames_until_flush(reader)?;
1491 parse_upload_archive_response(&frames)
1492}
1493
1494pub fn write_upload_archive_response(
1495 writer: &mut impl Write,
1496 response: &UploadArchiveResponse,
1497) -> Result<()> {
1498 match response {
1499 UploadArchiveResponse::Ack { sideband } => {
1500 write_pkt_line_payload(writer, b"ACK\n")?;
1501 write_sideband_stream(writer, sideband)?;
1502 }
1503 UploadArchiveResponse::Nack { message } => {
1504 validate_upload_archive_status_message(message)?;
1505 write_pkt_line_payload(writer, &line_from_str(&format!("NACK {message}")))?;
1506 writer.write_all(b"0000")?;
1507 }
1508 }
1509 Ok(())
1510}
1511
1512pub fn demux_upload_archive_response(response: &UploadArchiveResponse) -> Result<SideBandDemux> {
1513 match response {
1514 UploadArchiveResponse::Ack { sideband } => demux_sideband_packets(sideband),
1515 UploadArchiveResponse::Nack { message } => Err(GitError::InvalidFormat(format!(
1516 "upload-archive NACK: {message}"
1517 ))),
1518 }
1519}
1520
1521fn parse_pkt_len(bytes: &[u8]) -> Result<usize> {
1522 let mut len = 0usize;
1523 for byte in bytes {
1524 len = (len << 4) | hex_nibble(*byte)? as usize;
1525 }
1526 Ok(len)
1527}
1528
1529fn hex_nibble(byte: u8) -> Result<u8> {
1530 match byte {
1531 b'0'..=b'9' => Ok(byte - b'0'),
1532 b'a'..=b'f' => Ok(byte - b'a' + 10),
1533 b'A'..=b'F' => Ok(byte - b'A' + 10),
1534 _ => Err(GitError::InvalidFormat(format!(
1535 "invalid pkt-line length byte {byte:#04x}"
1536 ))),
1537 }
1538}
1539
1540#[derive(Debug, Clone, PartialEq, Eq)]
1541pub struct TransportHandshake {
1542 pub protocol: ProtocolVersion,
1543 pub capabilities: Vec<Capability>,
1544}
1545
1546#[derive(Debug, Clone, PartialEq, Eq)]
1547pub struct RefAdvertisement {
1548 pub oid: ObjectId,
1549 pub name: String,
1550 pub capabilities: Vec<Capability>,
1551}
1552
1553#[derive(Debug, Clone, PartialEq, Eq)]
1554pub struct DumbHttpRefRecord {
1555 pub oid: ObjectId,
1556 pub name: String,
1557 pub peeled: bool,
1558}
1559
1560#[derive(Debug, Clone, PartialEq, Eq)]
1561pub struct DumbHttpPackRecord {
1562 pub hash: ObjectId,
1563}
1564
1565#[derive(Debug, Clone, PartialEq, Eq)]
1566pub struct RefAdvertisementSet {
1567 pub protocol: ProtocolVersion,
1568 pub refs: Vec<RefAdvertisement>,
1569 pub shallow: Vec<ObjectId>,
1570}
1571
1572#[derive(Debug, Clone, PartialEq, Eq, Default)]
1573pub struct UploadPackRequest {
1574 pub wants: Vec<ObjectId>,
1575 pub capabilities: Vec<Capability>,
1576 pub shallow: Vec<ObjectId>,
1577 pub deepen: Option<u32>,
1578 pub deepen_since: Option<u64>,
1579 pub deepen_not: Vec<String>,
1580 pub filter: Option<String>,
1581}
1582
1583#[derive(Debug, Clone, PartialEq, Eq, Default)]
1584pub struct UploadPackFeatures {
1585 pub multi_ack: bool,
1586 pub multi_ack_detailed: bool,
1587 pub no_done: bool,
1588 pub thin_pack: bool,
1589 pub side_band: bool,
1590 pub side_band_64k: bool,
1591 pub ofs_delta: bool,
1592 pub shallow: bool,
1593 pub deepen_since: bool,
1594 pub deepen_not: bool,
1595 pub include_tag: bool,
1596 pub no_progress: bool,
1597 pub allow_tip_sha1_in_want: bool,
1598 pub allow_reachable_sha1_in_want: bool,
1599 pub filter: bool,
1600 pub agent: Option<String>,
1601 pub object_format: Option<ObjectFormat>,
1602 pub symrefs: Vec<String>,
1603 pub unknown: Vec<Capability>,
1604}
1605
1606#[derive(Debug, Clone, PartialEq, Eq, Default)]
1607pub struct UploadPackNegotiationRequest {
1608 pub haves: Vec<ObjectId>,
1609 pub done: bool,
1610}
1611
1612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1613pub enum UploadPackAckStatus {
1614 Continue,
1615 Common,
1616 Ready,
1617}
1618
1619#[derive(Debug, Clone, PartialEq, Eq)]
1620pub enum UploadPackAcknowledgment {
1621 Nak,
1622 Ack {
1623 oid: ObjectId,
1624 status: Option<UploadPackAckStatus>,
1625 },
1626}
1627
1628#[derive(Debug, Clone, PartialEq, Eq, Default)]
1629pub struct UploadPackPackfileResponse {
1630 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1631 pub sideband: Vec<SideBandPacket>,
1632}
1633
1634#[derive(Debug, Clone, PartialEq, Eq, Default)]
1635pub struct UploadPackRawPackfileResponse {
1636 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1637 pub packfile: Vec<u8>,
1638}
1639
1640#[derive(Debug, Clone, PartialEq, Eq, Default)]
1641pub struct UploadPackRawPackfileResponseHeader {
1642 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1643 pub pack_prefix: Vec<u8>,
1644}
1645
1646#[derive(Debug, Clone, PartialEq, Eq)]
1647pub struct ReceivePackCommand {
1648 pub old_id: ObjectId,
1649 pub new_id: ObjectId,
1650 pub name: String,
1651}
1652
1653#[derive(Debug, Clone, PartialEq, Eq, Default)]
1654pub struct ReceivePackRequest {
1655 pub shallow: Vec<ObjectId>,
1656 pub commands: Vec<ReceivePackCommand>,
1657 pub capabilities: Vec<Capability>,
1658}
1659
1660#[derive(Debug, Clone, PartialEq, Eq, Default)]
1661pub struct ReceivePackPushRequest {
1662 pub commands: ReceivePackRequest,
1663 pub push_options: Option<Vec<String>>,
1664 pub packfile: Vec<u8>,
1665}
1666
1667#[derive(Debug, Clone, PartialEq, Eq, Default)]
1668pub struct ReceivePackPushRequestHeader {
1669 pub commands: ReceivePackRequest,
1670 pub push_options: Option<Vec<String>>,
1671}
1672
1673#[derive(Debug, Clone, PartialEq, Eq, Default)]
1674pub struct ReceivePackPushRequestOptions {
1675 pub report_status: bool,
1676 pub report_status_v2: bool,
1677 pub atomic: bool,
1678 pub ofs_delta: bool,
1679 pub side_band_64k: bool,
1680 pub quiet: bool,
1681 pub agent: Option<String>,
1682 pub object_format: Option<ObjectFormat>,
1683 pub push_options: Vec<String>,
1684}
1685
1686#[derive(Debug, Clone, PartialEq, Eq, Default)]
1687pub struct ReceivePackFeatures {
1688 pub report_status: bool,
1689 pub report_status_v2: bool,
1690 pub delete_refs: bool,
1691 pub ofs_delta: bool,
1692 pub atomic: bool,
1693 pub push_options: bool,
1694 pub side_band_64k: bool,
1695 pub quiet: bool,
1696 pub no_thin: bool,
1697 pub agent: Option<String>,
1698 pub object_format: Option<ObjectFormat>,
1699 pub unknown: Vec<Capability>,
1700}
1701
1702#[derive(Debug, Clone, PartialEq, Eq)]
1703pub enum ReceivePackUnpackStatus {
1704 Ok,
1705 Error(String),
1706}
1707
1708#[derive(Debug, Clone, PartialEq, Eq)]
1709pub enum ReceivePackCommandStatus {
1710 Ok { name: String },
1711 Ng { name: String, message: String },
1712}
1713
1714#[derive(Debug, Clone, PartialEq, Eq)]
1715pub struct ReceivePackReportStatus {
1716 pub unpack: ReceivePackUnpackStatus,
1717 pub commands: Vec<ReceivePackCommandStatus>,
1718}
1719
1720#[derive(Debug, Clone, PartialEq, Eq, Default)]
1721pub struct ReceivePackCommandStatusV2Options {
1722 pub refname: Option<String>,
1723 pub old_oid: Option<ObjectId>,
1724 pub new_oid: Option<ObjectId>,
1725 pub forced_update: bool,
1726}
1727
1728#[derive(Debug, Clone, PartialEq, Eq)]
1729pub enum ReceivePackCommandStatusV2 {
1730 Ok {
1731 name: String,
1732 options: ReceivePackCommandStatusV2Options,
1733 },
1734 Ng {
1735 name: String,
1736 message: String,
1737 },
1738}
1739
1740#[derive(Debug, Clone, PartialEq, Eq)]
1741pub struct ReceivePackReportStatusV2 {
1742 pub unpack: ReceivePackUnpackStatus,
1743 pub commands: Vec<ReceivePackCommandStatusV2>,
1744}
1745
1746#[derive(Debug, Clone, PartialEq, Eq)]
1747pub struct ProtocolV2CommandRequest {
1748 pub command: String,
1749 pub capabilities: Vec<Capability>,
1750 pub arguments: Vec<Vec<u8>>,
1751}
1752
1753#[derive(Debug, Clone, PartialEq, Eq)]
1754pub enum ProtocolV2Request {
1755 Command(ProtocolV2CommandRequest),
1756 Done,
1757}
1758
1759#[derive(Debug, Clone, PartialEq, Eq)]
1760pub enum ProtocolV2Command {
1761 LsRefs(ProtocolV2LsRefsRequest),
1762 Fetch(ProtocolV2FetchRequest),
1763 ObjectInfo(ProtocolV2ObjectInfoRequest),
1764 Unknown(ProtocolV2CommandRequest),
1765}
1766
1767#[derive(Debug, Clone, PartialEq, Eq)]
1768pub enum ProtocolV2SessionRequest {
1769 Command(ProtocolV2Command),
1770 Done,
1771}
1772
1773#[derive(Debug, Clone, PartialEq, Eq, Default)]
1774pub struct ProtocolV2CommandOptions {
1775 pub agent: Option<String>,
1776 pub object_format: Option<ObjectFormat>,
1777 pub server_options: Vec<String>,
1778 pub extra: Vec<Capability>,
1779}
1780
1781#[derive(Debug, Clone, PartialEq, Eq, Default)]
1782pub struct ProtocolV2FetchFeatures {
1783 pub shallow: bool,
1784 pub wait_for_done: bool,
1785 pub filter: bool,
1786 pub ref_in_want: bool,
1787 pub sideband_all: bool,
1788 pub packfile_uris: bool,
1789 pub unknown: Vec<String>,
1790}
1791
1792#[derive(Debug, Clone, PartialEq, Eq, Default)]
1793pub struct ProtocolV2LsRefsFeatures {
1794 pub unborn: bool,
1795 pub unknown: Vec<String>,
1796}
1797
1798impl ProtocolV2CommandRequest {
1799 pub fn new(command: impl Into<String>) -> Result<Self> {
1800 let command = command.into();
1801 validate_capability_name(&command)?;
1802 Ok(Self {
1803 command,
1804 capabilities: Vec::new(),
1805 arguments: Vec::new(),
1806 })
1807 }
1808}
1809
1810#[derive(Debug, Clone, PartialEq, Eq, Default)]
1811pub struct ProtocolV2LsRefsRequest {
1812 pub peel: bool,
1813 pub symrefs: bool,
1814 pub unborn: bool,
1815 pub ref_prefixes: Vec<String>,
1816}
1817
1818#[derive(Debug, Clone, PartialEq, Eq)]
1819pub struct ProtocolV2LsRefsRef {
1820 pub oid: ObjectId,
1821 pub name: String,
1822 pub peeled: Option<ObjectId>,
1823 pub symref_target: Option<String>,
1824 pub attributes: Vec<String>,
1825}
1826
1827#[derive(Debug, Clone, PartialEq, Eq)]
1828pub enum ProtocolV2LsRefsRecord {
1829 Ref(ProtocolV2LsRefsRef),
1830 Unborn {
1831 name: String,
1832 symref_target: Option<String>,
1833 attributes: Vec<String>,
1834 },
1835}
1836
1837#[derive(Debug, Clone, PartialEq, Eq, Default)]
1838pub struct ProtocolV2FetchRequest {
1839 pub wants: Vec<ObjectId>,
1840 pub want_refs: Vec<String>,
1841 pub haves: Vec<ObjectId>,
1842 pub shallow: Vec<ObjectId>,
1843 pub deepen: Option<u32>,
1844 pub deepen_since: Option<u64>,
1845 pub deepen_not: Vec<String>,
1846 pub deepen_relative: bool,
1847 pub filter: Option<String>,
1848 pub packfile_uris: Option<String>,
1849 pub thin_pack: bool,
1850 pub no_progress: bool,
1851 pub include_tag: bool,
1852 pub ofs_delta: bool,
1853 pub sideband_all: bool,
1854 pub wait_for_done: bool,
1855 pub done: bool,
1856}
1857
1858#[derive(Debug, Clone, PartialEq, Eq)]
1859pub enum ProtocolV2FetchAcknowledgment {
1860 Nak,
1861 Ack(ObjectId),
1862 Ready,
1863}
1864
1865#[derive(Debug, Clone, PartialEq, Eq)]
1866pub enum ProtocolV2FetchShallowInfo {
1867 Shallow(ObjectId),
1868 Unshallow(ObjectId),
1869}
1870
1871#[derive(Debug, Clone, PartialEq, Eq)]
1872pub struct ProtocolV2FetchWantedRef {
1873 pub oid: ObjectId,
1874 pub name: String,
1875}
1876
1877#[derive(Debug, Clone, PartialEq, Eq)]
1878pub struct ProtocolV2FetchPackfileUri {
1879 pub pack_hash: ObjectId,
1880 pub uri: String,
1881}
1882
1883#[derive(Debug, Clone, PartialEq, Eq)]
1884pub enum ProtocolV2FetchResponseSection {
1885 Acknowledgments(Vec<ProtocolV2FetchAcknowledgment>),
1886 ShallowInfo(Vec<ProtocolV2FetchShallowInfo>),
1887 WantedRefs(Vec<ProtocolV2FetchWantedRef>),
1888 PackfileUris(Vec<ProtocolV2FetchPackfileUri>),
1889 Packfile(Vec<Vec<u8>>),
1890 Unknown { name: String, lines: Vec<Vec<u8>> },
1891}
1892
1893#[derive(Debug, Clone, PartialEq, Eq, Default)]
1894pub struct ProtocolV2FetchSidebandAllResponse {
1895 pub sections: Vec<ProtocolV2FetchResponseSection>,
1896 pub progress: Vec<Vec<u8>>,
1897}
1898
1899#[derive(Debug, Clone, PartialEq, Eq, Default)]
1900pub struct ProtocolV2FetchResponseHeader {
1901 pub sections: Vec<ProtocolV2FetchResponseSection>,
1902 pub has_packfile: bool,
1903}
1904
1905#[derive(Debug, Clone, PartialEq, Eq, Default)]
1906pub struct ProtocolV2ObjectInfoRequest {
1907 pub size: bool,
1908 pub oids: Vec<ObjectId>,
1909}
1910
1911#[derive(Debug, Clone, PartialEq, Eq)]
1912pub struct ProtocolV2ObjectInfoRecord {
1913 pub oid: ObjectId,
1914 pub size: u64,
1915}
1916
1917#[derive(Debug, Clone, PartialEq, Eq, Default)]
1918pub struct ProtocolV2ObjectInfoResponse {
1919 pub size: bool,
1920 pub records: Vec<ProtocolV2ObjectInfoRecord>,
1921}
1922
1923impl ProtocolV2LsRefsRequest {
1924 pub fn from_command_request(request: &ProtocolV2CommandRequest) -> Result<Self> {
1925 if request.command != "ls-refs" {
1926 return Err(GitError::InvalidFormat(format!(
1927 "expected ls-refs command, got {}",
1928 request.command
1929 )));
1930 }
1931 let mut out = Self::default();
1932 for argument in &request.arguments {
1933 let text = std::str::from_utf8(argument)
1934 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1935 match text {
1936 "peel" => out.peel = true,
1937 "symrefs" => out.symrefs = true,
1938 "unborn" => out.unborn = true,
1939 value if value.starts_with("ref-prefix ") => {
1940 let prefix = value
1941 .strip_prefix("ref-prefix ")
1942 .ok_or_else(|| GitError::InvalidFormat("invalid ref-prefix".into()))?;
1943 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1944 out.ref_prefixes.push(prefix.to_string());
1945 }
1946 other => {
1947 return Err(GitError::InvalidFormat(format!(
1948 "unsupported ls-refs argument {other}"
1949 )));
1950 }
1951 }
1952 }
1953 Ok(out)
1954 }
1955
1956 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1957 let mut request = ProtocolV2CommandRequest::new("ls-refs")?;
1958 if self.peel {
1959 request.arguments.push(b"peel".to_vec());
1960 }
1961 if self.symrefs {
1962 request.arguments.push(b"symrefs".to_vec());
1963 }
1964 if self.unborn {
1965 request.arguments.push(b"unborn".to_vec());
1966 }
1967 for prefix in &self.ref_prefixes {
1968 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1969 request
1970 .arguments
1971 .push(format!("ref-prefix {prefix}").into_bytes());
1972 }
1973 Ok(request)
1974 }
1975}
1976
1977impl ProtocolV2FetchRequest {
1978 pub fn from_command_request(
1979 format: ObjectFormat,
1980 request: &ProtocolV2CommandRequest,
1981 ) -> Result<Self> {
1982 if request.command != "fetch" {
1983 return Err(GitError::InvalidFormat(format!(
1984 "expected fetch command, got {}",
1985 request.command
1986 )));
1987 }
1988 let mut out = Self::default();
1989 for argument in &request.arguments {
1990 let text = std::str::from_utf8(argument)
1991 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1992 match text {
1993 "thin-pack" => out.thin_pack = true,
1994 "no-progress" => out.no_progress = true,
1995 "include-tag" => out.include_tag = true,
1996 "ofs-delta" => out.ofs_delta = true,
1997 "sideband-all" => out.sideband_all = true,
1998 "wait-for-done" => out.wait_for_done = true,
1999 "deepen-relative" => out.deepen_relative = true,
2000 "done" => out.done = true,
2001 value if value.starts_with("want ") => {
2002 out.wants
2003 .push(parse_oid_argument(format, "fetch want", value, "want ")?);
2004 }
2005 value if value.starts_with("want-ref ") => {
2006 let name = value
2007 .strip_prefix("want-ref ")
2008 .ok_or_else(|| GitError::InvalidFormat("invalid fetch want-ref".into()))?;
2009 validate_protocol_v2_token("fetch want-ref", name)?;
2010 out.want_refs.push(name.to_string());
2011 }
2012 value if value.starts_with("have ") => {
2013 out.haves
2014 .push(parse_oid_argument(format, "fetch have", value, "have ")?);
2015 }
2016 value if value.starts_with("shallow ") => {
2017 out.shallow.push(parse_oid_argument(
2018 format,
2019 "fetch shallow",
2020 value,
2021 "shallow ",
2022 )?);
2023 }
2024 value if value.starts_with("deepen ") => {
2025 if out.deepen.is_some() {
2026 return Err(GitError::InvalidFormat(
2027 "fetch request has duplicate deepen".into(),
2028 ));
2029 }
2030 out.deepen = Some(parse_u32_argument("fetch deepen", value, "deepen ")?);
2031 }
2032 value if value.starts_with("deepen-since ") => {
2033 if out.deepen_since.is_some() {
2034 return Err(GitError::InvalidFormat(
2035 "fetch request has duplicate deepen-since".into(),
2036 ));
2037 }
2038 out.deepen_since = Some(parse_u64_argument(
2039 "fetch deepen-since",
2040 value,
2041 "deepen-since ",
2042 )?);
2043 }
2044 value if value.starts_with("deepen-not ") => {
2045 let name = value.strip_prefix("deepen-not ").ok_or_else(|| {
2046 GitError::InvalidFormat("invalid fetch deepen-not".into())
2047 })?;
2048 validate_protocol_v2_token("fetch deepen-not", name)?;
2049 out.deepen_not.push(name.to_string());
2050 }
2051 value if value.starts_with("filter ") => {
2052 if out.filter.is_some() {
2053 return Err(GitError::InvalidFormat(
2054 "fetch request has duplicate filter".into(),
2055 ));
2056 }
2057 let filter = value
2058 .strip_prefix("filter ")
2059 .ok_or_else(|| GitError::InvalidFormat("invalid fetch filter".into()))?;
2060 validate_protocol_v2_token("fetch filter", filter)?;
2061 out.filter = Some(filter.to_string());
2062 }
2063 value if value.starts_with("packfile-uris ") => {
2064 if out.packfile_uris.is_some() {
2065 return Err(GitError::InvalidFormat(
2066 "fetch request has duplicate packfile-uris".into(),
2067 ));
2068 }
2069 let protocols = value.strip_prefix("packfile-uris ").ok_or_else(|| {
2070 GitError::InvalidFormat("invalid fetch packfile-uris".into())
2071 })?;
2072 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
2073 out.packfile_uris = Some(protocols.to_string());
2074 }
2075 other => {
2076 return Err(GitError::InvalidFormat(format!(
2077 "unsupported fetch argument {other}"
2078 )));
2079 }
2080 }
2081 }
2082 Ok(out)
2083 }
2084
2085 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
2086 let mut request = ProtocolV2CommandRequest::new("fetch")?;
2087 for oid in &self.wants {
2088 request.arguments.push(format!("want {oid}").into_bytes());
2089 }
2090 for name in &self.want_refs {
2091 validate_protocol_v2_token("fetch want-ref", name)?;
2092 request
2093 .arguments
2094 .push(format!("want-ref {name}").into_bytes());
2095 }
2096 for oid in &self.haves {
2097 request.arguments.push(format!("have {oid}").into_bytes());
2098 }
2099 for oid in &self.shallow {
2100 request
2101 .arguments
2102 .push(format!("shallow {oid}").into_bytes());
2103 }
2104 if let Some(deepen) = self.deepen {
2105 if deepen == 0 {
2106 return Err(GitError::InvalidFormat(
2107 "fetch deepen must be positive".into(),
2108 ));
2109 }
2110 request
2111 .arguments
2112 .push(format!("deepen {deepen}").into_bytes());
2113 }
2114 if let Some(deepen_since) = self.deepen_since {
2115 request
2116 .arguments
2117 .push(format!("deepen-since {deepen_since}").into_bytes());
2118 }
2119 for name in &self.deepen_not {
2120 validate_protocol_v2_token("fetch deepen-not", name)?;
2121 request
2122 .arguments
2123 .push(format!("deepen-not {name}").into_bytes());
2124 }
2125 if self.deepen_relative {
2126 request.arguments.push(b"deepen-relative".to_vec());
2127 }
2128 if let Some(filter) = &self.filter {
2129 validate_protocol_v2_token("fetch filter", filter)?;
2130 request
2131 .arguments
2132 .push(format!("filter {filter}").into_bytes());
2133 }
2134 if let Some(protocols) = &self.packfile_uris {
2135 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
2136 request
2137 .arguments
2138 .push(format!("packfile-uris {protocols}").into_bytes());
2139 }
2140 if self.thin_pack {
2141 request.arguments.push(b"thin-pack".to_vec());
2142 }
2143 if self.no_progress {
2144 request.arguments.push(b"no-progress".to_vec());
2145 }
2146 if self.include_tag {
2147 request.arguments.push(b"include-tag".to_vec());
2148 }
2149 if self.ofs_delta {
2150 request.arguments.push(b"ofs-delta".to_vec());
2151 }
2152 if self.sideband_all {
2153 request.arguments.push(b"sideband-all".to_vec());
2154 }
2155 if self.wait_for_done {
2156 request.arguments.push(b"wait-for-done".to_vec());
2157 }
2158 if self.done {
2159 request.arguments.push(b"done".to_vec());
2160 }
2161 Ok(request)
2162 }
2163}
2164
2165impl ProtocolV2ObjectInfoRequest {
2166 pub fn from_command_request(
2167 format: ObjectFormat,
2168 request: &ProtocolV2CommandRequest,
2169 ) -> Result<Self> {
2170 if request.command != "object-info" {
2171 return Err(GitError::InvalidFormat(format!(
2172 "expected object-info command, got {}",
2173 request.command
2174 )));
2175 }
2176 let mut out = Self::default();
2177 for argument in &request.arguments {
2178 let text = parse_protocol_v2_line_text("object-info request argument", argument)?;
2179 if text == "size" {
2180 if out.size {
2181 return Err(GitError::InvalidFormat(
2182 "object-info request has duplicate size argument".into(),
2183 ));
2184 }
2185 out.size = true;
2186 } else if text.starts_with("oid ") {
2187 out.oids
2188 .push(parse_oid_argument(format, "object-info oid", text, "oid ")?);
2189 } else {
2190 return Err(GitError::InvalidFormat(format!(
2191 "unsupported object-info request argument {text}"
2192 )));
2193 }
2194 }
2195 if !out.size {
2196 return Err(GitError::InvalidFormat(
2197 "object-info request is missing size argument".into(),
2198 ));
2199 }
2200 if out.oids.is_empty() {
2201 return Err(GitError::InvalidFormat(
2202 "object-info request is missing object ids".into(),
2203 ));
2204 }
2205 Ok(out)
2206 }
2207
2208 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
2209 if !self.size {
2210 return Err(GitError::InvalidFormat(
2211 "object-info request is missing size argument".into(),
2212 ));
2213 }
2214 if self.oids.is_empty() {
2215 return Err(GitError::InvalidFormat(
2216 "object-info request is missing object ids".into(),
2217 ));
2218 }
2219 let mut request = ProtocolV2CommandRequest::new("object-info")?;
2220 request.arguments.push(b"size".to_vec());
2221 for oid in &self.oids {
2222 request.arguments.push(format!("oid {oid}").into_bytes());
2223 }
2224 Ok(request)
2225 }
2226}
2227
2228pub fn parse_protocol_v2_advertisement(frames: &[PktLineFrame]) -> Result<TransportHandshake> {
2229 let Some((first, rest)) = frames.split_first() else {
2230 return Err(GitError::InvalidFormat(
2231 "protocol v2 advertisement is empty".into(),
2232 ));
2233 };
2234 match first {
2235 PktLineFrame::Data(payload) if trim_trailing_lf(payload) == b"version 2" => {}
2236 PktLineFrame::Data(_) => {
2237 return Err(GitError::InvalidFormat(
2238 "protocol v2 advertisement missing version line".into(),
2239 ));
2240 }
2241 _ => {
2242 return Err(GitError::InvalidFormat(
2243 "protocol v2 advertisement must start with a data line".into(),
2244 ));
2245 }
2246 }
2247
2248 let mut capabilities = Vec::new();
2249 let mut saw_flush = false;
2250 for (idx, frame) in rest.iter().enumerate() {
2251 match frame {
2252 PktLineFrame::Data(payload) => {
2253 if saw_flush {
2254 return Err(GitError::InvalidFormat(
2255 "protocol v2 advertisement has data after flush".into(),
2256 ));
2257 }
2258 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2259 }
2260 PktLineFrame::Flush => {
2261 saw_flush = true;
2262 if idx + 1 != rest.len() {
2263 return Err(GitError::InvalidFormat(
2264 "protocol v2 advertisement has frames after flush".into(),
2265 ));
2266 }
2267 }
2268 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2269 return Err(GitError::InvalidFormat(
2270 "protocol v2 advertisement contains a non-flush control packet".into(),
2271 ));
2272 }
2273 }
2274 }
2275 if !saw_flush {
2276 return Err(GitError::InvalidFormat(
2277 "protocol v2 advertisement missing flush".into(),
2278 ));
2279 }
2280
2281 Ok(TransportHandshake {
2282 protocol: ProtocolVersion::V2,
2283 capabilities,
2284 })
2285}
2286
2287pub fn encode_protocol_v2_advertisement(
2288 handshake: &TransportHandshake,
2289) -> Result<Vec<PktLineFrame>> {
2290 if handshake.protocol != ProtocolVersion::V2 {
2291 return Err(GitError::InvalidFormat(
2292 "protocol v2 advertisement requires a v2 handshake".into(),
2293 ));
2294 }
2295 let mut frames = vec![PktLineFrame::data(line_from_str("version 2"))?];
2296 for capability in &handshake.capabilities {
2297 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2298 capability,
2299 )?))?);
2300 }
2301 frames.push(PktLineFrame::Flush);
2302 Ok(frames)
2303}
2304
2305pub fn read_protocol_v2_advertisement(reader: &mut impl Read) -> Result<TransportHandshake> {
2306 let frames = read_pkt_line_frames_until_flush(reader)?;
2307 parse_protocol_v2_advertisement(&frames)
2308}
2309
2310pub fn write_protocol_v2_advertisement(
2311 writer: &mut impl Write,
2312 handshake: &TransportHandshake,
2313) -> Result<()> {
2314 if handshake.protocol != ProtocolVersion::V2 {
2315 return Err(GitError::InvalidFormat(
2316 "protocol v2 advertisement requires a v2 handshake".into(),
2317 ));
2318 }
2319 write_pkt_line_payload(writer, b"version 2\n")?;
2320 for capability in &handshake.capabilities {
2321 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2322 }
2323 writer.write_all(b"0000")?;
2324 Ok(())
2325}
2326
2327pub fn parse_protocol_v2_command_request(
2328 frames: &[PktLineFrame],
2329) -> Result<ProtocolV2CommandRequest> {
2330 let Some((first, rest)) = frames.split_first() else {
2331 return Err(GitError::InvalidFormat(
2332 "protocol v2 command request is empty".into(),
2333 ));
2334 };
2335 let command = match first {
2336 PktLineFrame::Data(payload) => parse_protocol_v2_command_line(payload)?,
2337 _ => {
2338 return Err(GitError::InvalidFormat(
2339 "protocol v2 command request must start with a command line".into(),
2340 ));
2341 }
2342 };
2343
2344 let mut capabilities = Vec::new();
2345 let mut arguments = Vec::new();
2346 let mut in_arguments = false;
2347 let mut saw_flush = false;
2348 for (idx, frame) in rest.iter().enumerate() {
2349 match frame {
2350 PktLineFrame::Data(payload) if !in_arguments => {
2351 if saw_flush {
2352 return Err(GitError::InvalidFormat(
2353 "protocol v2 command request has data after flush".into(),
2354 ));
2355 }
2356 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2357 }
2358 PktLineFrame::Data(payload) => {
2359 if saw_flush {
2360 return Err(GitError::InvalidFormat(
2361 "protocol v2 command request has data after flush".into(),
2362 ));
2363 }
2364 let argument = trim_trailing_lf(payload);
2365 if argument.is_empty() {
2366 return Err(GitError::InvalidFormat(
2367 "protocol v2 command argument is empty".into(),
2368 ));
2369 }
2370 if argument
2371 .iter()
2372 .any(|byte| matches!(*byte, b'\n' | b'\r' | 0))
2373 {
2374 return Err(GitError::InvalidFormat(
2375 "protocol v2 command argument contains a delimiter byte".into(),
2376 ));
2377 }
2378 arguments.push(argument.to_vec());
2379 }
2380 PktLineFrame::Delimiter => {
2381 if in_arguments {
2382 return Err(GitError::InvalidFormat(format!(
2383 "expected flush after {} arguments",
2384 command
2385 )));
2386 }
2387 if saw_flush {
2388 return Err(GitError::InvalidFormat(
2389 "protocol v2 command request has delimiter after flush".into(),
2390 ));
2391 }
2392 in_arguments = true;
2393 }
2394 PktLineFrame::Flush => {
2395 saw_flush = true;
2396 if idx + 1 != rest.len() {
2397 return Err(GitError::InvalidFormat(
2398 "protocol v2 command request has frames after flush".into(),
2399 ));
2400 }
2401 }
2402 PktLineFrame::ResponseEnd => {
2403 return Err(GitError::InvalidFormat(
2404 "protocol v2 command request contains response-end".into(),
2405 ));
2406 }
2407 }
2408 }
2409 if !saw_flush {
2410 return Err(GitError::InvalidFormat(
2411 "protocol v2 command request missing flush".into(),
2412 ));
2413 }
2414
2415 Ok(ProtocolV2CommandRequest {
2416 command,
2417 capabilities,
2418 arguments,
2419 })
2420}
2421
2422pub fn encode_protocol_v2_command_request(
2423 request: &ProtocolV2CommandRequest,
2424) -> Result<Vec<PktLineFrame>> {
2425 validate_capability_name(&request.command)?;
2426 let mut frames = Vec::new();
2427 frames.push(PktLineFrame::data(line_from_str(&format!(
2428 "command={}",
2429 request.command
2430 )))?);
2431 for capability in &request.capabilities {
2432 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2433 capability,
2434 )?))?);
2435 }
2436 if !request.arguments.is_empty() {
2437 frames.push(PktLineFrame::Delimiter);
2438 for argument in &request.arguments {
2439 validate_protocol_v2_argument(argument)?;
2440 let mut payload = argument.clone();
2441 payload.push(b'\n');
2442 frames.push(PktLineFrame::data(payload)?);
2443 }
2444 }
2445 frames.push(PktLineFrame::Flush);
2446 Ok(frames)
2447}
2448
2449pub fn parse_protocol_v2_request(frames: &[PktLineFrame]) -> Result<ProtocolV2Request> {
2450 if matches!(frames, [PktLineFrame::Flush]) {
2451 return Ok(ProtocolV2Request::Done);
2452 }
2453 parse_protocol_v2_command_request(frames).map(ProtocolV2Request::Command)
2454}
2455
2456pub fn encode_protocol_v2_request(request: &ProtocolV2Request) -> Result<Vec<PktLineFrame>> {
2457 match request {
2458 ProtocolV2Request::Command(command) => encode_protocol_v2_command_request(command),
2459 ProtocolV2Request::Done => Ok(vec![PktLineFrame::Flush]),
2460 }
2461}
2462
2463pub fn read_protocol_v2_request(reader: &mut impl Read) -> Result<ProtocolV2Request> {
2464 let frames = read_pkt_line_frames_until_flush(reader)?;
2465 parse_protocol_v2_request(&frames)
2466}
2467
2468pub fn write_protocol_v2_request(
2469 writer: &mut impl Write,
2470 request: &ProtocolV2Request,
2471) -> Result<()> {
2472 match request {
2473 ProtocolV2Request::Command(command) => write_protocol_v2_command_request(writer, command),
2474 ProtocolV2Request::Done => {
2475 writer.write_all(b"0000")?;
2476 Ok(())
2477 }
2478 }
2479}
2480
2481pub fn read_protocol_v2_command_request(
2482 reader: &mut impl Read,
2483) -> Result<ProtocolV2CommandRequest> {
2484 let mut frames = Vec::new();
2485 loop {
2486 let Some(frame) = read_pkt_line_frame(reader)? else {
2487 if let Some(command) = frames.first().and_then(|frame| match frame {
2488 PktLineFrame::Data(payload) => parse_protocol_v2_command_line(payload).ok(),
2489 _ => None,
2490 }) && frames
2491 .iter()
2492 .any(|frame| matches!(frame, PktLineFrame::Delimiter))
2493 {
2494 return Err(GitError::InvalidFormat(format!(
2495 "expected flush after {} arguments",
2496 command
2497 )));
2498 }
2499 return Err(GitError::InvalidFormat(
2500 "pkt-line stream ended before control packet".into(),
2501 ));
2502 };
2503 let done = matches!(frame, PktLineFrame::Flush);
2504 frames.push(frame);
2505 if done {
2506 break;
2507 }
2508 }
2509 parse_protocol_v2_command_request(&frames)
2510}
2511
2512pub fn write_protocol_v2_command_request(
2513 writer: &mut impl Write,
2514 request: &ProtocolV2CommandRequest,
2515) -> Result<()> {
2516 validate_capability_name(&request.command)?;
2517 write_pkt_line_payload(
2518 writer,
2519 &line_from_str(&format!("command={}", request.command)),
2520 )?;
2521 for capability in &request.capabilities {
2522 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2523 }
2524 if !request.arguments.is_empty() {
2525 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2526 for argument in &request.arguments {
2527 validate_protocol_v2_argument(argument)?;
2528 let mut payload = argument.clone();
2529 payload.push(b'\n');
2530 write_pkt_line_payload(writer, &payload)?;
2531 }
2532 }
2533 writer.write_all(b"0000")?;
2534 Ok(())
2535}
2536
2537pub fn read_protocol_v2_ls_refs_request(reader: &mut impl Read) -> Result<ProtocolV2LsRefsRequest> {
2538 let request = read_protocol_v2_command_request(reader)?;
2539 ProtocolV2LsRefsRequest::from_command_request(&request)
2540}
2541
2542pub fn write_protocol_v2_ls_refs_request(
2543 writer: &mut impl Write,
2544 request: &ProtocolV2LsRefsRequest,
2545) -> Result<()> {
2546 let command = request.to_command_request()?;
2547 write_protocol_v2_command_request(writer, &command)
2548}
2549
2550pub fn parse_protocol_v2_ls_refs_response(
2551 format: ObjectFormat,
2552 frames: &[PktLineFrame],
2553) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2554 let mut records = Vec::new();
2555 let mut saw_flush = false;
2556 for (idx, frame) in frames.iter().enumerate() {
2557 match frame {
2558 PktLineFrame::Data(payload) => {
2559 if saw_flush {
2560 return Err(GitError::InvalidFormat(
2561 "ls-refs response has data after flush".into(),
2562 ));
2563 }
2564 records.push(parse_protocol_v2_ls_refs_line(format, payload)?);
2565 }
2566 PktLineFrame::Flush => {
2567 saw_flush = true;
2568 if !flush_terminates_protocol_v2_response(frames, idx) {
2569 return Err(GitError::InvalidFormat(
2570 "ls-refs response has frames after flush".into(),
2571 ));
2572 }
2573 }
2574 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2575 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2576 return Err(GitError::InvalidFormat(
2577 "ls-refs response contains a non-flush control packet".into(),
2578 ));
2579 }
2580 }
2581 }
2582 if !saw_flush {
2583 return Err(GitError::InvalidFormat(
2584 "ls-refs response missing flush".into(),
2585 ));
2586 }
2587 Ok(records)
2588}
2589
2590pub fn encode_protocol_v2_ls_refs_response(
2591 records: &[ProtocolV2LsRefsRecord],
2592) -> Result<Vec<PktLineFrame>> {
2593 let mut frames = Vec::new();
2594 for record in records {
2595 frames.push(PktLineFrame::data(line_from_str(
2596 &format_protocol_v2_ls_refs_record(record)?,
2597 ))?);
2598 }
2599 frames.push(PktLineFrame::Flush);
2600 Ok(frames)
2601}
2602
2603pub fn read_protocol_v2_ls_refs_response(
2604 format: ObjectFormat,
2605 reader: &mut impl Read,
2606) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2607 let frames = read_pkt_line_frames_until_flush(reader)?;
2608 parse_protocol_v2_ls_refs_response(format, &frames)
2609}
2610
2611pub fn write_protocol_v2_ls_refs_response(
2612 writer: &mut impl Write,
2613 records: &[ProtocolV2LsRefsRecord],
2614) -> Result<()> {
2615 for record in records {
2616 write_pkt_line_payload(
2617 writer,
2618 &line_from_str(&format_protocol_v2_ls_refs_record(record)?),
2619 )?;
2620 }
2621 writer.write_all(b"0000")?;
2622 Ok(())
2623}
2624
2625pub fn read_protocol_v2_ls_refs_response_until_response_end(
2626 format: ObjectFormat,
2627 reader: &mut impl Read,
2628) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2629 let frames = read_pkt_line_frames_until_response_end(reader)?;
2630 parse_protocol_v2_ls_refs_response(format, &frames)
2631}
2632
2633pub fn write_protocol_v2_ls_refs_response_with_response_end(
2634 writer: &mut impl Write,
2635 records: &[ProtocolV2LsRefsRecord],
2636) -> Result<()> {
2637 write_protocol_v2_ls_refs_response(writer, records)?;
2638 writer.write_all(b"0002")?;
2639 Ok(())
2640}
2641
2642pub fn exchange_protocol_v2_ls_refs(
2643 format: ObjectFormat,
2644 reader: &mut impl Read,
2645 writer: &mut impl Write,
2646 request: &ProtocolV2LsRefsRequest,
2647) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2648 write_protocol_v2_ls_refs_request(writer, request)?;
2649 writer.flush()?;
2650 read_protocol_v2_ls_refs_response(format, reader)
2651}
2652
2653pub fn protocol_v2_ls_refs_records_to_ref_advertisement_set(
2669 records: &[ProtocolV2LsRefsRecord],
2670) -> Result<RefAdvertisementSet> {
2671 let mut refs: Vec<RefAdvertisement> = Vec::new();
2672 let mut symrefs: Vec<Capability> = Vec::new();
2673 for record in records {
2674 match record {
2675 ProtocolV2LsRefsRecord::Ref(reference) => {
2676 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
2677 refs.push(RefAdvertisement {
2678 oid: reference.oid,
2679 name: reference.name.clone(),
2680 capabilities: Vec::new(),
2681 });
2682 if let Some(peeled) = &reference.peeled {
2683 refs.push(RefAdvertisement {
2684 oid: peeled.clone(),
2685 name: format!("{}^{{}}", reference.name),
2686 capabilities: Vec::new(),
2687 });
2688 }
2689 if let Some(target) = &reference.symref_target {
2690 symrefs.push(protocol_v2_symref_capability(&reference.name, target)?);
2691 }
2692 }
2693 ProtocolV2LsRefsRecord::Unborn {
2694 name,
2695 symref_target,
2696 ..
2697 } => {
2698 validate_protocol_v2_token("ls-refs ref name", name)?;
2699 if let Some(target) = symref_target {
2700 symrefs.push(protocol_v2_symref_capability(name, target)?);
2701 }
2702 }
2703 }
2704 }
2705 if !symrefs.is_empty() {
2706 if let Some(first) = refs.first_mut() {
2707 first.capabilities = symrefs;
2708 } else {
2709 return Err(GitError::InvalidFormat(
2710 "ls-refs response advertised symrefs without any concrete refs".into(),
2711 ));
2712 }
2713 }
2714 Ok(RefAdvertisementSet {
2715 protocol: ProtocolVersion::V2,
2716 refs,
2717 shallow: Vec::new(),
2718 })
2719}
2720
2721pub fn parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2726 format: ObjectFormat,
2727 frames: &[PktLineFrame],
2728) -> Result<RefAdvertisementSet> {
2729 let records = parse_protocol_v2_ls_refs_response(format, frames)?;
2730 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2731}
2732
2733pub fn read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2736 format: ObjectFormat,
2737 reader: &mut impl Read,
2738) -> Result<RefAdvertisementSet> {
2739 let records = read_protocol_v2_ls_refs_response(format, reader)?;
2740 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2741}
2742
2743fn protocol_v2_symref_capability(name: &str, target: &str) -> Result<Capability> {
2744 validate_protocol_v2_token("ls-refs symref-target", target)?;
2745 Ok(Capability {
2746 name: "symref".into(),
2747 value: Some(format!("{name}:{target}")),
2748 })
2749}
2750
2751pub fn read_protocol_v2_fetch_request(
2752 format: ObjectFormat,
2753 reader: &mut impl Read,
2754) -> Result<ProtocolV2FetchRequest> {
2755 let request = read_protocol_v2_command_request(reader)?;
2756 ProtocolV2FetchRequest::from_command_request(format, &request)
2757}
2758
2759pub fn write_protocol_v2_fetch_request(
2760 writer: &mut impl Write,
2761 request: &ProtocolV2FetchRequest,
2762) -> Result<()> {
2763 let command = request.to_command_request()?;
2764 write_protocol_v2_command_request(writer, &command)
2765}
2766
2767pub fn read_protocol_v2_object_info_request(
2768 format: ObjectFormat,
2769 reader: &mut impl Read,
2770) -> Result<ProtocolV2ObjectInfoRequest> {
2771 let request = read_protocol_v2_command_request(reader)?;
2772 ProtocolV2ObjectInfoRequest::from_command_request(format, &request)
2773}
2774
2775pub fn write_protocol_v2_object_info_request(
2776 writer: &mut impl Write,
2777 request: &ProtocolV2ObjectInfoRequest,
2778) -> Result<()> {
2779 let command = request.to_command_request()?;
2780 write_protocol_v2_command_request(writer, &command)
2781}
2782
2783pub fn parse_protocol_v2_fetch_response(
2784 format: ObjectFormat,
2785 frames: &[PktLineFrame],
2786) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2787 let mut sections = Vec::new();
2788 let mut current: Option<(String, Vec<Vec<u8>>)> = None;
2789 let mut saw_flush = false;
2790 for (idx, frame) in frames.iter().enumerate() {
2791 match frame {
2792 PktLineFrame::Data(payload) => {
2793 if saw_flush {
2794 return Err(GitError::InvalidFormat(
2795 "fetch response has data after flush".into(),
2796 ));
2797 }
2798 if let Some((_name, lines)) = &mut current {
2799 lines.push(payload.clone());
2800 } else {
2801 let name = parse_fetch_section_header(payload)?;
2802 current = Some((name, Vec::new()));
2803 }
2804 }
2805 PktLineFrame::Delimiter => {
2806 if saw_flush {
2807 return Err(GitError::InvalidFormat(
2808 "fetch response has delimiter after flush".into(),
2809 ));
2810 }
2811 let Some((name, lines)) = current.take() else {
2812 return Err(GitError::InvalidFormat(
2813 "fetch response has delimiter before section".into(),
2814 ));
2815 };
2816 sections.push(parse_fetch_section(format, name, lines)?);
2817 }
2818 PktLineFrame::Flush => {
2819 saw_flush = true;
2820 if !flush_terminates_protocol_v2_response(frames, idx) {
2821 return Err(GitError::InvalidFormat(
2822 "fetch response has frames after flush".into(),
2823 ));
2824 }
2825 if let Some((name, lines)) = current.take() {
2826 sections.push(parse_fetch_section(format, name, lines)?);
2827 }
2828 }
2829 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2830 PktLineFrame::ResponseEnd => {
2831 return Err(GitError::InvalidFormat(
2832 "fetch response contains response-end".into(),
2833 ));
2834 }
2835 }
2836 }
2837 if !saw_flush {
2838 return Err(GitError::InvalidFormat(
2839 "fetch response missing flush".into(),
2840 ));
2841 }
2842 Ok(sections)
2843}
2844
2845pub fn encode_protocol_v2_fetch_response(
2846 sections: &[ProtocolV2FetchResponseSection],
2847) -> Result<Vec<PktLineFrame>> {
2848 let mut frames = Vec::new();
2849 for (idx, section) in sections.iter().enumerate() {
2850 if idx != 0 {
2851 frames.push(PktLineFrame::Delimiter);
2852 }
2853 frames.push(PktLineFrame::data(line_from_str(
2854 protocol_v2_fetch_section_name(section),
2855 ))?);
2856 for line in format_protocol_v2_fetch_section_lines(section)? {
2857 frames.push(PktLineFrame::data(line)?);
2858 }
2859 }
2860 frames.push(PktLineFrame::Flush);
2861 Ok(frames)
2862}
2863
2864pub fn parse_protocol_v2_fetch_sideband_all_response(
2865 format: ObjectFormat,
2866 frames: &[PktLineFrame],
2867) -> Result<ProtocolV2FetchSidebandAllResponse> {
2868 let mut demuxed = Vec::new();
2869 let mut progress = Vec::new();
2870 let mut in_packfile = false;
2871 for frame in frames {
2872 match frame {
2873 PktLineFrame::Data(payload) if in_packfile => {
2874 demuxed.push(PktLineFrame::Data(payload.clone()));
2875 }
2876 PktLineFrame::Data(payload) => {
2877 let packet = parse_sideband_packet(payload)?;
2878 match packet.channel {
2879 SideBandChannel::Data => {
2880 if trim_trailing_lf(&packet.data) == b"packfile" {
2881 in_packfile = true;
2882 }
2883 demuxed.push(PktLineFrame::Data(packet.data));
2884 }
2885 SideBandChannel::Progress => progress.push(packet.data),
2886 SideBandChannel::Fatal => {
2887 let message = String::from_utf8_lossy(&packet.data).into_owned();
2888 return Err(GitError::InvalidFormat(format!(
2889 "sideband fatal: {message}"
2890 )));
2891 }
2892 }
2893 }
2894 PktLineFrame::Delimiter => {
2895 in_packfile = false;
2896 demuxed.push(PktLineFrame::Delimiter);
2897 }
2898 PktLineFrame::Flush => {
2899 in_packfile = false;
2900 demuxed.push(PktLineFrame::Flush);
2901 }
2902 PktLineFrame::ResponseEnd => {
2903 in_packfile = false;
2904 demuxed.push(PktLineFrame::ResponseEnd);
2905 }
2906 }
2907 }
2908 Ok(ProtocolV2FetchSidebandAllResponse {
2909 sections: parse_protocol_v2_fetch_response(format, &demuxed)?,
2910 progress,
2911 })
2912}
2913
2914pub fn encode_protocol_v2_fetch_sideband_all_response(
2915 sections: &[ProtocolV2FetchResponseSection],
2916) -> Result<Vec<PktLineFrame>> {
2917 let frames = encode_protocol_v2_fetch_response(sections)?;
2918 let mut encoded = Vec::new();
2919 let mut in_packfile = false;
2920 for frame in frames {
2921 match frame {
2922 PktLineFrame::Data(payload) if in_packfile => {
2923 encoded.push(PktLineFrame::Data(payload));
2924 }
2925 PktLineFrame::Data(payload) => {
2926 if trim_trailing_lf(&payload) == b"packfile" {
2927 in_packfile = true;
2928 }
2929 encoded.push(PktLineFrame::data(encode_sideband_packet(
2930 &SideBandPacket {
2931 channel: SideBandChannel::Data,
2932 data: payload,
2933 },
2934 )?)?);
2935 }
2936 PktLineFrame::Delimiter => {
2937 in_packfile = false;
2938 encoded.push(PktLineFrame::Delimiter);
2939 }
2940 PktLineFrame::Flush => {
2941 in_packfile = false;
2942 encoded.push(PktLineFrame::Flush);
2943 }
2944 PktLineFrame::ResponseEnd => {
2945 in_packfile = false;
2946 encoded.push(PktLineFrame::ResponseEnd);
2947 }
2948 }
2949 }
2950 Ok(encoded)
2951}
2952
2953pub fn read_protocol_v2_fetch_response(
2954 format: ObjectFormat,
2955 reader: &mut impl Read,
2956) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2957 let frames = read_pkt_line_frames_until_flush(reader)?;
2958 parse_protocol_v2_fetch_response(format, &frames)
2959}
2960
2961pub fn read_protocol_v2_fetch_response_header(
2962 format: ObjectFormat,
2963 reader: &mut impl Read,
2964 sideband_all: bool,
2965) -> Result<ProtocolV2FetchResponseHeader> {
2966 let mut sections = Vec::new();
2967 let mut current: Option<(String, Vec<Vec<u8>>)> = None;
2968 loop {
2969 let frame = read_protocol_v2_fetch_metadata_frame(reader, sideband_all)?;
2970 match frame {
2971 PktLineFrame::Data(payload) => {
2972 if let Some((_name, lines)) = &mut current {
2973 lines.push(payload);
2974 } else {
2975 let name = parse_fetch_section_header(&payload)?;
2976 if name == "packfile" {
2977 return Ok(ProtocolV2FetchResponseHeader {
2978 sections,
2979 has_packfile: true,
2980 });
2981 }
2982 current = Some((name, Vec::new()));
2983 }
2984 }
2985 PktLineFrame::Delimiter => {
2986 let Some((name, lines)) = current.take() else {
2987 return Err(GitError::InvalidFormat(
2988 "fetch response has delimiter before section".into(),
2989 ));
2990 };
2991 sections.push(parse_fetch_section(format, name, lines)?);
2992 }
2993 PktLineFrame::Flush => {
2994 if let Some((name, lines)) = current.take() {
2995 sections.push(parse_fetch_section(format, name, lines)?);
2996 }
2997 return Ok(ProtocolV2FetchResponseHeader {
2998 sections,
2999 has_packfile: false,
3000 });
3001 }
3002 PktLineFrame::ResponseEnd => {
3003 return Err(GitError::InvalidFormat(
3004 "fetch response contains response-end".into(),
3005 ));
3006 }
3007 }
3008 }
3009}
3010
3011fn read_protocol_v2_fetch_metadata_frame(
3012 reader: &mut impl Read,
3013 sideband_all: bool,
3014) -> Result<PktLineFrame> {
3015 loop {
3016 let frame = read_pkt_line_frame(reader)?
3017 .ok_or_else(|| GitError::InvalidFormat("fetch response ended before flush".into()))?;
3018 if sideband_all && let PktLineFrame::Data(payload) = frame {
3019 let packet = parse_sideband_packet(&payload)?;
3020 match packet.channel {
3021 SideBandChannel::Data => return Ok(PktLineFrame::Data(packet.data)),
3022 SideBandChannel::Progress => continue,
3023 SideBandChannel::Fatal => {
3024 let message = String::from_utf8_lossy(&packet.data).into_owned();
3025 return Err(GitError::InvalidFormat(format!(
3026 "sideband fatal: {message}"
3027 )));
3028 }
3029 }
3030 }
3031 return Ok(frame);
3032 }
3033}
3034
3035pub fn write_protocol_v2_fetch_response(
3036 writer: &mut impl Write,
3037 sections: &[ProtocolV2FetchResponseSection],
3038) -> Result<()> {
3039 write_protocol_v2_fetch_response_inner(writer, sections, false, false)
3040}
3041
3042pub fn read_protocol_v2_fetch_sideband_all_response(
3043 format: ObjectFormat,
3044 reader: &mut impl Read,
3045) -> Result<ProtocolV2FetchSidebandAllResponse> {
3046 let frames = read_pkt_line_frames_until_flush(reader)?;
3047 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
3048}
3049
3050pub fn write_protocol_v2_fetch_sideband_all_response(
3051 writer: &mut impl Write,
3052 sections: &[ProtocolV2FetchResponseSection],
3053) -> Result<()> {
3054 write_protocol_v2_fetch_response_inner(writer, sections, true, false)
3055}
3056
3057pub fn read_protocol_v2_fetch_response_until_response_end(
3058 format: ObjectFormat,
3059 reader: &mut impl Read,
3060) -> Result<Vec<ProtocolV2FetchResponseSection>> {
3061 let frames = read_pkt_line_frames_until_response_end(reader)?;
3062 parse_protocol_v2_fetch_response(format, &frames)
3063}
3064
3065pub fn write_protocol_v2_fetch_response_with_response_end(
3066 writer: &mut impl Write,
3067 sections: &[ProtocolV2FetchResponseSection],
3068) -> Result<()> {
3069 write_protocol_v2_fetch_response_inner(writer, sections, false, true)
3070}
3071
3072pub fn read_protocol_v2_fetch_sideband_all_response_until_response_end(
3073 format: ObjectFormat,
3074 reader: &mut impl Read,
3075) -> Result<ProtocolV2FetchSidebandAllResponse> {
3076 let frames = read_pkt_line_frames_until_response_end(reader)?;
3077 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
3078}
3079
3080pub fn write_protocol_v2_fetch_sideband_all_response_with_response_end(
3081 writer: &mut impl Write,
3082 sections: &[ProtocolV2FetchResponseSection],
3083) -> Result<()> {
3084 write_protocol_v2_fetch_response_inner(writer, sections, true, true)
3085}
3086
3087fn write_protocol_v2_fetch_response_inner(
3088 writer: &mut impl Write,
3089 sections: &[ProtocolV2FetchResponseSection],
3090 sideband_all: bool,
3091 response_end: bool,
3092) -> Result<()> {
3093 let mut in_packfile = false;
3094 for (idx, section) in sections.iter().enumerate() {
3095 if idx != 0 {
3096 in_packfile = false;
3097 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
3098 }
3099 write_protocol_v2_fetch_payload(
3100 writer,
3101 &line_from_str(protocol_v2_fetch_section_name(section)),
3102 sideband_all,
3103 &mut in_packfile,
3104 )?;
3105 for payload in format_protocol_v2_fetch_section_lines(section)? {
3106 write_protocol_v2_fetch_payload(writer, &payload, sideband_all, &mut in_packfile)?;
3107 }
3108 }
3109 writer.write_all(b"0000")?;
3110 if response_end {
3111 writer.write_all(b"0002")?;
3112 }
3113 Ok(())
3114}
3115
3116fn write_protocol_v2_fetch_payload(
3117 writer: &mut impl Write,
3118 payload: &[u8],
3119 sideband_all: bool,
3120 in_packfile: &mut bool,
3121) -> Result<()> {
3122 if sideband_all && !*in_packfile {
3123 if trim_trailing_lf(payload) == b"packfile" {
3124 *in_packfile = true;
3125 }
3126 write_sideband_payload(writer, SideBandChannel::Data, payload)
3127 } else {
3128 write_pkt_line_payload(writer, payload)
3129 }
3130}
3131
3132pub fn exchange_protocol_v2_fetch(
3133 format: ObjectFormat,
3134 reader: &mut impl Read,
3135 writer: &mut impl Write,
3136 request: &ProtocolV2FetchRequest,
3137) -> Result<Vec<ProtocolV2FetchResponseSection>> {
3138 write_protocol_v2_fetch_request(writer, request)?;
3139 writer.flush()?;
3140 read_protocol_v2_fetch_response(format, reader)
3141}
3142
3143pub fn parse_protocol_v2_object_info_response(
3144 format: ObjectFormat,
3145 frames: &[PktLineFrame],
3146) -> Result<ProtocolV2ObjectInfoResponse> {
3147 let Some((first, rest)) = frames.split_first() else {
3148 return Err(GitError::InvalidFormat(
3149 "object-info response is empty".into(),
3150 ));
3151 };
3152 let PktLineFrame::Data(attrs) = first else {
3153 return Err(GitError::InvalidFormat(
3154 "object-info response must start with attributes".into(),
3155 ));
3156 };
3157 let attrs = parse_protocol_v2_line_text("object-info response attributes", attrs)?;
3158 let mut response = ProtocolV2ObjectInfoResponse::default();
3159 for attr in attrs.split(' ') {
3160 validate_protocol_v2_token("object-info response attribute", attr)?;
3161 match attr {
3162 "size" => {
3163 if response.size {
3164 return Err(GitError::InvalidFormat(
3165 "object-info response has duplicate size attribute".into(),
3166 ));
3167 }
3168 response.size = true;
3169 }
3170 other => {
3171 return Err(GitError::InvalidFormat(format!(
3172 "unsupported object-info response attribute {other}"
3173 )));
3174 }
3175 }
3176 }
3177 if !response.size {
3178 return Err(GitError::InvalidFormat(
3179 "object-info response is missing size attribute".into(),
3180 ));
3181 }
3182
3183 let mut saw_flush = false;
3184 for (idx, frame) in rest.iter().enumerate() {
3185 match frame {
3186 PktLineFrame::Data(payload) if !saw_flush => {
3187 response
3188 .records
3189 .push(parse_protocol_v2_object_info_record(format, payload)?);
3190 }
3191 PktLineFrame::Data(_) => {
3192 return Err(GitError::InvalidFormat(
3193 "object-info response has data after flush".into(),
3194 ));
3195 }
3196 PktLineFrame::Flush => {
3197 saw_flush = true;
3198 if idx + 1 != rest.len() {
3199 return Err(GitError::InvalidFormat(
3200 "object-info response has frames after flush".into(),
3201 ));
3202 }
3203 }
3204 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3205 return Err(GitError::InvalidFormat(
3206 "object-info response contains a non-flush control packet".into(),
3207 ));
3208 }
3209 }
3210 }
3211 if !saw_flush {
3212 return Err(GitError::InvalidFormat(
3213 "object-info response missing flush".into(),
3214 ));
3215 }
3216 Ok(response)
3217}
3218
3219pub fn encode_protocol_v2_object_info_response(
3220 response: &ProtocolV2ObjectInfoResponse,
3221) -> Result<Vec<PktLineFrame>> {
3222 if !response.size {
3223 return Err(GitError::InvalidFormat(
3224 "object-info response is missing size attribute".into(),
3225 ));
3226 }
3227 let mut frames = Vec::new();
3228 frames.push(PktLineFrame::data(line_from_str("size"))?);
3229 for record in &response.records {
3230 frames.push(PktLineFrame::data(line_from_str(&format!(
3231 "{} {}",
3232 record.oid, record.size
3233 )))?);
3234 }
3235 frames.push(PktLineFrame::Flush);
3236 Ok(frames)
3237}
3238
3239pub fn read_protocol_v2_object_info_response(
3240 format: ObjectFormat,
3241 reader: &mut impl Read,
3242) -> Result<ProtocolV2ObjectInfoResponse> {
3243 let frames = read_pkt_line_frames_until_flush(reader)?;
3244 parse_protocol_v2_object_info_response(format, &frames)
3245}
3246
3247pub fn write_protocol_v2_object_info_response(
3248 writer: &mut impl Write,
3249 response: &ProtocolV2ObjectInfoResponse,
3250) -> Result<()> {
3251 if !response.size {
3252 return Err(GitError::InvalidFormat(
3253 "object-info response is missing size attribute".into(),
3254 ));
3255 }
3256 write_pkt_line_payload(writer, b"size\n")?;
3257 for record in &response.records {
3258 write_pkt_line_payload(
3259 writer,
3260 &line_from_str(&format!("{} {}", record.oid, record.size)),
3261 )?;
3262 }
3263 writer.write_all(b"0000")?;
3264 Ok(())
3265}
3266
3267pub fn exchange_protocol_v2_object_info(
3268 format: ObjectFormat,
3269 reader: &mut impl Read,
3270 writer: &mut impl Write,
3271 request: &ProtocolV2ObjectInfoRequest,
3272) -> Result<ProtocolV2ObjectInfoResponse> {
3273 write_protocol_v2_object_info_request(writer, request)?;
3274 writer.flush()?;
3275 read_protocol_v2_object_info_response(format, reader)
3276}
3277
3278pub fn demux_protocol_v2_fetch_packfile(
3279 sections: &[ProtocolV2FetchResponseSection],
3280) -> Result<Option<SideBandDemux>> {
3281 let mut packfile = None;
3282 for section in sections {
3283 if let ProtocolV2FetchResponseSection::Packfile(lines) = section {
3284 if packfile.is_some() {
3285 return Err(GitError::InvalidFormat(
3286 "fetch response has duplicate packfile sections".into(),
3287 ));
3288 }
3289 packfile = Some(parse_and_demux_sideband_packets(lines)?);
3290 }
3291 }
3292 Ok(packfile)
3293}
3294
3295pub fn protocol_v2_object_format(capabilities: &[Capability]) -> Result<ObjectFormat> {
3296 let mut format = None;
3297 for capability in capabilities {
3298 if capability.name != "object-format" {
3299 continue;
3300 }
3301 if format.is_some() {
3302 return Err(GitError::InvalidFormat(
3303 "protocol v2 has duplicate object-format capabilities".into(),
3304 ));
3305 }
3306 let Some(value) = &capability.value else {
3307 return Err(GitError::InvalidFormat(
3308 "protocol v2 object-format capability is missing a value".into(),
3309 ));
3310 };
3311 format = Some(value.parse::<ObjectFormat>()?);
3312 }
3313 Ok(format.unwrap_or(ObjectFormat::Sha1))
3314}
3315
3316pub fn validate_protocol_v2_command_request_capabilities(
3317 handshake: &TransportHandshake,
3318 request: &ProtocolV2CommandRequest,
3319) -> Result<()> {
3320 if handshake.protocol != ProtocolVersion::V2 {
3321 return Err(GitError::InvalidFormat(
3322 "protocol v2 command validation requires a v2 handshake".into(),
3323 ));
3324 }
3325 let advertised =
3326 protocol_v2_capability(&handshake.capabilities, &request.command).ok_or_else(|| {
3327 GitError::InvalidFormat(format!("unadvertised command {}", request.command))
3328 })?;
3329 if advertised.name.is_empty() {
3330 return Err(GitError::InvalidFormat(
3331 "advertised command capability is empty".into(),
3332 ));
3333 }
3334 parse_protocol_v2_command_options(&request.capabilities)?;
3335
3336 for capability in &request.capabilities {
3337 let advertised = protocol_v2_capability(&handshake.capabilities, &capability.name)
3338 .ok_or_else(|| {
3339 GitError::InvalidFormat(format!(
3340 "unadvertised protocol v2 capability {}",
3341 capability.name
3342 ))
3343 })?;
3344 if capability.name == "object-format" {
3345 validate_protocol_v2_object_format_request(advertised, capability)?;
3346 }
3347 }
3348 Ok(())
3349}
3350
3351pub fn parse_protocol_v2_command_options(
3352 capabilities: &[Capability],
3353) -> Result<ProtocolV2CommandOptions> {
3354 let mut out = ProtocolV2CommandOptions::default();
3355 for capability in capabilities {
3356 match capability.name.as_str() {
3357 "agent" => {
3358 if out.agent.is_some() {
3359 return Err(GitError::InvalidFormat(
3360 "protocol v2 command has duplicate agent capabilities".into(),
3361 ));
3362 }
3363 let Some(value) = &capability.value else {
3364 return Err(GitError::InvalidFormat(
3365 "protocol v2 agent capability is missing a value".into(),
3366 ));
3367 };
3368 validate_protocol_v2_capability_value(value)?;
3369 out.agent = Some(value.clone());
3370 }
3371 "object-format" => {
3372 if out.object_format.is_some() {
3373 return Err(GitError::InvalidFormat(
3374 "protocol v2 command has duplicate object-format capabilities".into(),
3375 ));
3376 }
3377 let Some(value) = &capability.value else {
3378 return Err(GitError::InvalidFormat(
3379 "protocol v2 object-format capability is missing a value".into(),
3380 ));
3381 };
3382 out.object_format = Some(value.parse::<ObjectFormat>()?);
3383 }
3384 "server-option" => {
3385 let Some(value) = &capability.value else {
3386 return Err(GitError::InvalidFormat(
3387 "protocol v2 server-option capability is missing a value".into(),
3388 ));
3389 };
3390 validate_protocol_v2_capability_value(value)?;
3391 out.server_options.push(value.clone());
3392 }
3393 _ => out.extra.push(capability.clone()),
3394 }
3395 }
3396 Ok(out)
3397}
3398
3399pub fn encode_protocol_v2_command_options(
3400 options: &ProtocolV2CommandOptions,
3401) -> Result<Vec<Capability>> {
3402 let mut capabilities = Vec::new();
3403 if let Some(agent) = &options.agent {
3404 validate_protocol_v2_capability_value(agent)?;
3405 capabilities.push(Capability {
3406 name: "agent".into(),
3407 value: Some(agent.clone()),
3408 });
3409 }
3410 if let Some(format) = options.object_format {
3411 capabilities.push(Capability {
3412 name: "object-format".into(),
3413 value: Some(format.name().into()),
3414 });
3415 }
3416 for option in &options.server_options {
3417 validate_protocol_v2_capability_value(option)?;
3418 capabilities.push(Capability {
3419 name: "server-option".into(),
3420 value: Some(option.clone()),
3421 });
3422 }
3423 for capability in &options.extra {
3424 if matches!(
3425 capability.name.as_str(),
3426 "agent" | "object-format" | "server-option"
3427 ) {
3428 return Err(GitError::InvalidFormat(format!(
3429 "protocol v2 extra capability duplicates known capability {}",
3430 capability.name
3431 )));
3432 }
3433 encode_protocol_v2_capability(capability)?;
3434 capabilities.push(capability.clone());
3435 }
3436 Ok(capabilities)
3437}
3438
3439pub fn parse_protocol_v2_ls_refs_features(
3440 capabilities: &[Capability],
3441) -> Result<Option<ProtocolV2LsRefsFeatures>> {
3442 let mut ls_refs = None;
3443 for capability in capabilities {
3444 if capability.name != "ls-refs" {
3445 continue;
3446 }
3447 if ls_refs.is_some() {
3448 return Err(GitError::InvalidFormat(
3449 "protocol v2 has duplicate ls-refs capabilities".into(),
3450 ));
3451 }
3452 ls_refs = Some(parse_protocol_v2_ls_refs_feature_value(
3453 capability.value.as_deref(),
3454 )?);
3455 }
3456 Ok(ls_refs)
3457}
3458
3459pub fn encode_protocol_v2_ls_refs_capability(
3460 features: &ProtocolV2LsRefsFeatures,
3461) -> Result<Capability> {
3462 let mut values = Vec::new();
3463 if features.unborn {
3464 values.push("unborn".to_string());
3465 }
3466 for feature in &features.unknown {
3467 validate_protocol_v2_token("ls-refs feature", feature)?;
3468 if feature == "unborn" {
3469 return Err(GitError::InvalidFormat(
3470 "ls-refs unknown features must not duplicate known feature unborn".into(),
3471 ));
3472 }
3473 values.push(feature.clone());
3474 }
3475 Ok(Capability {
3476 name: "ls-refs".into(),
3477 value: (!values.is_empty()).then(|| values.join(" ")),
3478 })
3479}
3480
3481pub fn validate_protocol_v2_ls_refs_request_features(
3482 features: &ProtocolV2LsRefsFeatures,
3483 request: &ProtocolV2LsRefsRequest,
3484) -> Result<()> {
3485 if request.unborn && !features.unborn {
3486 return Err(GitError::InvalidFormat(
3487 "ls-refs request uses unborn without advertised unborn feature".into(),
3488 ));
3489 }
3490 Ok(())
3491}
3492
3493pub fn validate_protocol_v2_ls_refs_command_request(
3494 handshake: &TransportHandshake,
3495 request: &ProtocolV2CommandRequest,
3496) -> Result<ProtocolV2LsRefsRequest> {
3497 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3498 let ls_refs = ProtocolV2LsRefsRequest::from_command_request(request)?;
3499 let features = parse_protocol_v2_ls_refs_features(&handshake.capabilities)?
3500 .ok_or_else(|| GitError::InvalidFormat("ls-refs command was not advertised".into()))?;
3501 validate_protocol_v2_ls_refs_request_features(&features, &ls_refs)?;
3502 Ok(ls_refs)
3503}
3504
3505pub fn parse_protocol_v2_fetch_features(
3506 capabilities: &[Capability],
3507) -> Result<Option<ProtocolV2FetchFeatures>> {
3508 let mut fetch = None;
3509 for capability in capabilities {
3510 if capability.name != "fetch" {
3511 continue;
3512 }
3513 if fetch.is_some() {
3514 return Err(GitError::InvalidFormat(
3515 "protocol v2 has duplicate fetch capabilities".into(),
3516 ));
3517 }
3518 fetch = Some(parse_protocol_v2_fetch_feature_value(
3519 capability.value.as_deref(),
3520 )?);
3521 }
3522 Ok(fetch)
3523}
3524
3525pub fn encode_protocol_v2_fetch_capability(
3526 features: &ProtocolV2FetchFeatures,
3527) -> Result<Capability> {
3528 let mut values = Vec::new();
3529 if features.shallow {
3530 values.push("shallow".to_string());
3531 }
3532 if features.wait_for_done {
3533 values.push("wait-for-done".to_string());
3534 }
3535 if features.filter {
3536 values.push("filter".to_string());
3537 }
3538 if features.ref_in_want {
3539 values.push("ref-in-want".to_string());
3540 }
3541 if features.sideband_all {
3542 values.push("sideband-all".to_string());
3543 }
3544 if features.packfile_uris {
3545 values.push("packfile-uris".to_string());
3546 }
3547 for feature in &features.unknown {
3548 validate_protocol_v2_token("fetch feature", feature)?;
3549 if matches!(
3550 feature.as_str(),
3551 "shallow"
3552 | "wait-for-done"
3553 | "filter"
3554 | "ref-in-want"
3555 | "sideband-all"
3556 | "packfile-uris"
3557 ) {
3558 return Err(GitError::InvalidFormat(format!(
3559 "fetch unknown features must not duplicate known feature {feature}"
3560 )));
3561 }
3562 values.push(feature.clone());
3563 }
3564 Ok(Capability {
3565 name: "fetch".into(),
3566 value: (!values.is_empty()).then(|| values.join(" ")),
3567 })
3568}
3569
3570pub fn validate_protocol_v2_fetch_request_features(
3571 features: &ProtocolV2FetchFeatures,
3572 request: &ProtocolV2FetchRequest,
3573) -> Result<()> {
3574 if !features.shallow
3575 && (!request.shallow.is_empty()
3576 || request.deepen.is_some()
3577 || request.deepen_since.is_some()
3578 || !request.deepen_not.is_empty()
3579 || request.deepen_relative)
3580 {
3581 return Err(GitError::InvalidFormat(
3582 "fetch request uses shallow/deepen arguments without advertised shallow feature".into(),
3583 ));
3584 }
3585 if !features.filter && request.filter.is_some() {
3586 return Err(GitError::InvalidFormat(
3587 "fetch request uses filter without advertised filter feature".into(),
3588 ));
3589 }
3590 if !features.ref_in_want && !request.want_refs.is_empty() {
3591 return Err(GitError::InvalidFormat(
3592 "fetch request uses want-ref without advertised ref-in-want feature".into(),
3593 ));
3594 }
3595 if !features.sideband_all && request.sideband_all {
3596 return Err(GitError::InvalidFormat(
3597 "fetch request uses sideband-all without advertised sideband-all feature".into(),
3598 ));
3599 }
3600 if !features.packfile_uris && request.packfile_uris.is_some() {
3601 return Err(GitError::InvalidFormat(
3602 "fetch request uses packfile-uris without advertised packfile-uris feature".into(),
3603 ));
3604 }
3605 if !features.wait_for_done && request.wait_for_done {
3606 return Err(GitError::InvalidFormat(
3607 "fetch request uses wait-for-done without advertised wait-for-done feature".into(),
3608 ));
3609 }
3610 Ok(())
3611}
3612
3613pub fn validate_protocol_v2_fetch_command_request(
3614 handshake: &TransportHandshake,
3615 format: ObjectFormat,
3616 request: &ProtocolV2CommandRequest,
3617) -> Result<ProtocolV2FetchRequest> {
3618 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3619 let fetch = ProtocolV2FetchRequest::from_command_request(format, request)?;
3620 let features = parse_protocol_v2_fetch_features(&handshake.capabilities)?
3621 .ok_or_else(|| GitError::InvalidFormat("fetch command was not advertised".into()))?;
3622 validate_protocol_v2_fetch_request_features(&features, &fetch)?;
3623 Ok(fetch)
3624}
3625
3626pub fn validate_protocol_v2_object_info_command_request(
3627 handshake: &TransportHandshake,
3628 format: ObjectFormat,
3629 request: &ProtocolV2CommandRequest,
3630) -> Result<ProtocolV2ObjectInfoRequest> {
3631 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3632 let object_info = ProtocolV2ObjectInfoRequest::from_command_request(format, request)?;
3633 protocol_v2_capability(&handshake.capabilities, "object-info")
3634 .ok_or_else(|| GitError::InvalidFormat("object-info command was not advertised".into()))?;
3635 Ok(object_info)
3636}
3637
3638pub fn classify_protocol_v2_command_request(
3639 handshake: &TransportHandshake,
3640 format: ObjectFormat,
3641 request: &ProtocolV2CommandRequest,
3642) -> Result<ProtocolV2Command> {
3643 match request.command.as_str() {
3644 "ls-refs" => validate_protocol_v2_ls_refs_command_request(handshake, request)
3645 .map(ProtocolV2Command::LsRefs),
3646 "fetch" => validate_protocol_v2_fetch_command_request(handshake, format, request)
3647 .map(ProtocolV2Command::Fetch),
3648 "object-info" => {
3649 validate_protocol_v2_object_info_command_request(handshake, format, request)
3650 .map(ProtocolV2Command::ObjectInfo)
3651 }
3652 _ => {
3653 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3654 Ok(ProtocolV2Command::Unknown(request.clone()))
3655 }
3656 }
3657}
3658
3659pub fn classify_protocol_v2_request(
3660 handshake: &TransportHandshake,
3661 format: ObjectFormat,
3662 request: &ProtocolV2Request,
3663) -> Result<ProtocolV2SessionRequest> {
3664 match request {
3665 ProtocolV2Request::Command(command) => {
3666 classify_protocol_v2_command_request(handshake, format, command)
3667 .map(ProtocolV2SessionRequest::Command)
3668 }
3669 ProtocolV2Request::Done => Ok(ProtocolV2SessionRequest::Done),
3670 }
3671}
3672
3673pub fn read_protocol_v2_session_request(
3674 handshake: &TransportHandshake,
3675 format: ObjectFormat,
3676 reader: &mut impl Read,
3677) -> Result<ProtocolV2SessionRequest> {
3678 let request = read_protocol_v2_request(reader)?;
3679 classify_protocol_v2_request(handshake, format, &request)
3680}
3681
3682fn protocol_v2_capability<'a>(
3683 capabilities: &'a [Capability],
3684 name: &str,
3685) -> Option<&'a Capability> {
3686 capabilities
3687 .iter()
3688 .find(|capability| capability.name == name)
3689}
3690
3691fn validate_protocol_v2_object_format_request(
3692 advertised: &Capability,
3693 requested: &Capability,
3694) -> Result<()> {
3695 let Some(advertised) = &advertised.value else {
3696 return Err(GitError::InvalidFormat(
3697 "advertised object-format capability is missing a value".into(),
3698 ));
3699 };
3700 let Some(requested) = &requested.value else {
3701 return Err(GitError::InvalidFormat(
3702 "requested object-format capability is missing a value".into(),
3703 ));
3704 };
3705 if advertised != requested {
3706 return Err(GitError::InvalidFormat(format!(
3707 "requested object-format {requested} does not match advertised {advertised}"
3708 )));
3709 }
3710 Ok(())
3711}
3712
3713fn parse_protocol_v2_ls_refs_feature_value(
3714 value: Option<&str>,
3715) -> Result<ProtocolV2LsRefsFeatures> {
3716 let mut out = ProtocolV2LsRefsFeatures::default();
3717 let Some(value) = value else {
3718 return Ok(out);
3719 };
3720 if value.is_empty() {
3721 return Err(GitError::InvalidFormat(
3722 "protocol v2 ls-refs capability value is empty".into(),
3723 ));
3724 }
3725 for feature in value.split(' ') {
3726 validate_protocol_v2_token("ls-refs feature", feature)?;
3727 match feature {
3728 "unborn" => out.unborn = true,
3729 other => out.unknown.push(other.to_string()),
3730 }
3731 }
3732 Ok(out)
3733}
3734
3735fn parse_protocol_v2_fetch_feature_value(value: Option<&str>) -> Result<ProtocolV2FetchFeatures> {
3736 let mut out = ProtocolV2FetchFeatures::default();
3737 let Some(value) = value else {
3738 return Ok(out);
3739 };
3740 if value.is_empty() {
3741 return Err(GitError::InvalidFormat(
3742 "protocol v2 fetch capability value is empty".into(),
3743 ));
3744 }
3745 for feature in value.split(' ') {
3746 validate_protocol_v2_token("fetch feature", feature)?;
3747 match feature {
3748 "shallow" => out.shallow = true,
3749 "wait-for-done" => out.wait_for_done = true,
3750 "filter" => out.filter = true,
3751 "ref-in-want" => out.ref_in_want = true,
3752 "sideband-all" => out.sideband_all = true,
3753 "packfile-uris" => out.packfile_uris = true,
3754 other => out.unknown.push(other.to_string()),
3755 }
3756 }
3757 Ok(out)
3758}
3759
3760pub fn parse_capabilities(input: &[u8]) -> Result<Vec<Capability>> {
3761 let input = trim_trailing_lf(input);
3762 if input.is_empty() {
3763 return Ok(Vec::new());
3764 }
3765 let text =
3766 std::str::from_utf8(input).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3767 text.split(' ')
3768 .map(parse_capability_token)
3769 .collect::<Result<Vec<_>>>()
3770}
3771
3772pub fn encode_capabilities(capabilities: &[Capability]) -> Result<Vec<u8>> {
3773 let mut out = Vec::new();
3774 for (idx, capability) in capabilities.iter().enumerate() {
3775 validate_capability_field("capability name", &capability.name)?;
3776 if idx != 0 {
3777 out.push(b' ');
3778 }
3779 out.extend_from_slice(capability.name.as_bytes());
3780 if let Some(value) = &capability.value {
3781 validate_capability_field("capability value", value)?;
3782 out.push(b'=');
3783 out.extend_from_slice(value.as_bytes());
3784 }
3785 }
3786 Ok(out)
3787}
3788
3789pub fn parse_ref_advertisement(format: ObjectFormat, payload: &[u8]) -> Result<RefAdvertisement> {
3790 let payload = trim_trailing_lf(payload);
3791 let (reference, capabilities) = match payload.iter().position(|byte| *byte == 0) {
3792 Some(idx) => (&payload[..idx], parse_capabilities(&payload[idx + 1..])?),
3793 None => (payload, Vec::new()),
3794 };
3795 let text =
3796 std::str::from_utf8(reference).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3797 let (oid, name) = text
3798 .split_once(' ')
3799 .ok_or_else(|| GitError::InvalidFormat("advertised ref is missing name".into()))?;
3800 if name.is_empty() {
3801 return Err(GitError::InvalidFormat(
3802 "advertised ref name is empty".into(),
3803 ));
3804 }
3805 Ok(RefAdvertisement {
3806 oid: ObjectId::from_hex(format, oid)?,
3807 name: name.to_string(),
3808 capabilities,
3809 })
3810}
3811
3812pub fn encode_ref_advertisement(advertisement: &RefAdvertisement) -> Result<Vec<u8>> {
3813 validate_protocol_v2_token("advertised ref name", &advertisement.name)?;
3814 let mut out = advertisement.oid.to_string().into_bytes();
3815 out.push(b' ');
3816 out.extend_from_slice(advertisement.name.as_bytes());
3817 if !advertisement.capabilities.is_empty() {
3818 out.push(0);
3819 out.extend_from_slice(&encode_capabilities(&advertisement.capabilities)?);
3820 }
3821 out.push(b'\n');
3822 Ok(out)
3823}
3824
3825pub fn parse_ref_advertisements(
3826 format: ObjectFormat,
3827 frames: &[PktLineFrame],
3828) -> Result<Vec<RefAdvertisement>> {
3829 Ok(parse_ref_advertisement_set(format, frames)?.refs)
3830}
3831
3832pub fn parse_ref_advertisement_set(
3833 format: ObjectFormat,
3834 frames: &[PktLineFrame],
3835) -> Result<RefAdvertisementSet> {
3836 let mut set = RefAdvertisementSet {
3837 protocol: ProtocolVersion::V0,
3838 refs: Vec::new(),
3839 shallow: Vec::new(),
3840 };
3841 let mut saw_flush = false;
3842 let mut in_shallow = false;
3843 for (idx, frame) in frames.iter().enumerate() {
3844 match frame {
3845 PktLineFrame::Data(payload) if !saw_flush => {
3846 let trimmed = trim_trailing_lf(payload);
3847 if trimmed == b"version 1" {
3848 if idx != 0 {
3849 return Err(GitError::InvalidFormat(
3850 "advertised ref protocol version must be the first line".into(),
3851 ));
3852 }
3853 set.protocol = ProtocolVersion::V1;
3854 continue;
3855 }
3856 if trimmed.starts_with(b"version ") {
3857 return Err(GitError::InvalidFormat(
3858 "unsupported advertised ref protocol version".into(),
3859 ));
3860 }
3861 if trimmed.starts_with(b"shallow ") {
3862 if set.refs.is_empty() {
3863 return Err(GitError::InvalidFormat(
3864 "advertised shallow refs must follow advertised refs".into(),
3865 ));
3866 }
3867 let text = parse_protocol_v2_line_text("advertised shallow ref", payload)?;
3868 set.shallow.push(parse_oid_argument(
3869 format,
3870 "advertised shallow ref",
3871 text,
3872 "shallow ",
3873 )?);
3874 in_shallow = true;
3875 continue;
3876 }
3877 if in_shallow {
3878 return Err(GitError::InvalidFormat(
3879 "advertised refs must not follow shallow refs".into(),
3880 ));
3881 }
3882 let advertisement = parse_ref_advertisement(format, payload)?;
3883 if !set.refs.is_empty() && !advertisement.capabilities.is_empty() {
3884 return Err(GitError::InvalidFormat(
3885 "advertised ref capabilities must appear on the first ref".into(),
3886 ));
3887 }
3888 set.refs.push(advertisement);
3889 }
3890 PktLineFrame::Data(_) => {
3891 return Err(GitError::InvalidFormat(
3892 "advertised ref stream has data after flush".into(),
3893 ));
3894 }
3895 PktLineFrame::Flush => {
3896 saw_flush = true;
3897 if idx + 1 != frames.len() {
3898 return Err(GitError::InvalidFormat(
3899 "advertised ref stream has frames after flush".into(),
3900 ));
3901 }
3902 }
3903 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3904 return Err(GitError::InvalidFormat(
3905 "advertised ref stream contains a non-flush control packet".into(),
3906 ));
3907 }
3908 }
3909 }
3910 if !saw_flush {
3911 return Err(GitError::InvalidFormat(
3912 "advertised ref stream missing flush".into(),
3913 ));
3914 }
3915 Ok(set)
3916}
3917
3918pub fn encode_ref_advertisements(advertisements: &[RefAdvertisement]) -> Result<Vec<PktLineFrame>> {
3919 encode_ref_advertisement_set(&RefAdvertisementSet {
3920 protocol: ProtocolVersion::V0,
3921 refs: advertisements.to_vec(),
3922 shallow: Vec::new(),
3923 })
3924}
3925
3926pub fn encode_ref_advertisement_set(set: &RefAdvertisementSet) -> Result<Vec<PktLineFrame>> {
3927 let mut frames = Vec::new();
3928 match set.protocol {
3929 ProtocolVersion::V0 => {}
3930 ProtocolVersion::V1 => frames.push(PktLineFrame::data(line_from_str("version 1"))?),
3931 ProtocolVersion::V2 => {
3932 return Err(GitError::InvalidFormat(
3933 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3934 ));
3935 }
3936 }
3937 if set.refs.is_empty() && !set.shallow.is_empty() {
3938 return Err(GitError::InvalidFormat(
3939 "advertised shallow refs require advertised refs".into(),
3940 ));
3941 }
3942 for (idx, advertisement) in set.refs.iter().enumerate() {
3943 if idx != 0 && !advertisement.capabilities.is_empty() {
3944 return Err(GitError::InvalidFormat(
3945 "advertised ref capabilities must appear on the first ref".into(),
3946 ));
3947 }
3948 frames.push(PktLineFrame::data(encode_ref_advertisement(
3949 advertisement,
3950 )?)?);
3951 }
3952 for oid in &set.shallow {
3953 frames.push(PktLineFrame::data(line_from_str(&format!(
3954 "shallow {oid}"
3955 )))?);
3956 }
3957 frames.push(PktLineFrame::Flush);
3958 Ok(frames)
3959}
3960
3961pub fn read_ref_advertisements(
3962 format: ObjectFormat,
3963 reader: &mut impl Read,
3964) -> Result<Vec<RefAdvertisement>> {
3965 let frames = read_pkt_line_frames_until_flush(reader)?;
3966 parse_ref_advertisements(format, &frames)
3967}
3968
3969pub fn read_ref_advertisement_set(
3970 format: ObjectFormat,
3971 reader: &mut impl Read,
3972) -> Result<RefAdvertisementSet> {
3973 let frames = read_pkt_line_frames_until_flush(reader)?;
3974 parse_ref_advertisement_set(format, &frames)
3975}
3976
3977pub fn write_ref_advertisements(
3978 writer: &mut impl Write,
3979 advertisements: &[RefAdvertisement],
3980) -> Result<()> {
3981 write_ref_advertisement_stream(writer, ProtocolVersion::V0, advertisements, &[])
3982}
3983
3984pub fn write_ref_advertisement_set(
3985 writer: &mut impl Write,
3986 set: &RefAdvertisementSet,
3987) -> Result<()> {
3988 write_ref_advertisement_stream(writer, set.protocol, &set.refs, &set.shallow)
3989}
3990
3991fn write_ref_advertisement_stream(
3992 writer: &mut impl Write,
3993 protocol: ProtocolVersion,
3994 refs: &[RefAdvertisement],
3995 shallow: &[ObjectId],
3996) -> Result<()> {
3997 match protocol {
3998 ProtocolVersion::V0 => {}
3999 ProtocolVersion::V1 => write_pkt_line_payload(writer, b"version 1\n")?,
4000 ProtocolVersion::V2 => {
4001 return Err(GitError::InvalidFormat(
4002 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
4003 ));
4004 }
4005 }
4006 if refs.is_empty() && !shallow.is_empty() {
4007 return Err(GitError::InvalidFormat(
4008 "advertised shallow refs require advertised refs".into(),
4009 ));
4010 }
4011 for (idx, advertisement) in refs.iter().enumerate() {
4012 if idx != 0 && !advertisement.capabilities.is_empty() {
4013 return Err(GitError::InvalidFormat(
4014 "advertised ref capabilities must appear on the first ref".into(),
4015 ));
4016 }
4017 write_pkt_line_payload(writer, &encode_ref_advertisement(advertisement)?)?;
4018 }
4019 for oid in shallow {
4020 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
4021 }
4022 writer.write_all(b"0000")?;
4023 Ok(())
4024}
4025
4026pub fn parse_dumb_http_info_refs(
4027 format: ObjectFormat,
4028 input: &[u8],
4029) -> Result<Vec<DumbHttpRefRecord>> {
4030 if input.is_empty() {
4031 return Ok(Vec::new());
4032 }
4033 input
4034 .split_inclusive(|byte| *byte == b'\n')
4035 .map(|line| parse_dumb_http_info_ref_record(format, line))
4036 .collect()
4037}
4038
4039pub fn encode_dumb_http_info_refs(records: &[DumbHttpRefRecord]) -> Result<Vec<u8>> {
4040 let mut out = Vec::new();
4041 for record in records {
4042 validate_dumb_http_ref_name(&record.name)?;
4043 out.extend_from_slice(record.oid.to_string().as_bytes());
4044 out.push(b'\t');
4045 out.extend_from_slice(record.name.as_bytes());
4046 if record.peeled {
4047 out.extend_from_slice(b"^{}");
4048 }
4049 out.push(b'\n');
4050 }
4051 Ok(out)
4052}
4053
4054pub fn read_dumb_http_info_refs(
4055 format: ObjectFormat,
4056 reader: &mut impl Read,
4057) -> Result<Vec<DumbHttpRefRecord>> {
4058 let mut input = Vec::new();
4059 reader.read_to_end(&mut input)?;
4060 parse_dumb_http_info_refs(format, &input)
4061}
4062
4063pub fn write_dumb_http_info_refs(
4064 writer: &mut impl Write,
4065 records: &[DumbHttpRefRecord],
4066) -> Result<()> {
4067 for record in records {
4068 validate_dumb_http_ref_name(&record.name)?;
4069 writer.write_all(record.oid.to_string().as_bytes())?;
4070 writer.write_all(b"\t")?;
4071 writer.write_all(record.name.as_bytes())?;
4072 if record.peeled {
4073 writer.write_all(b"^{}")?;
4074 }
4075 writer.write_all(b"\n")?;
4076 }
4077 Ok(())
4078}
4079
4080pub fn parse_dumb_http_alternates(input: &[u8]) -> Result<Vec<String>> {
4081 if input.is_empty() {
4082 return Ok(Vec::new());
4083 }
4084 input
4085 .split_inclusive(|byte| *byte == b'\n')
4086 .map(parse_dumb_http_alternate)
4087 .collect()
4088}
4089
4090pub fn encode_dumb_http_alternates(alternates: &[String]) -> Result<Vec<u8>> {
4091 let mut out = Vec::new();
4092 for alternate in alternates {
4093 validate_dumb_http_alternate(alternate)?;
4094 out.extend_from_slice(alternate.as_bytes());
4095 out.push(b'\n');
4096 }
4097 Ok(out)
4098}
4099
4100pub fn read_dumb_http_alternates(reader: &mut impl Read) -> Result<Vec<String>> {
4101 let mut input = Vec::new();
4102 reader.read_to_end(&mut input)?;
4103 parse_dumb_http_alternates(&input)
4104}
4105
4106pub fn write_dumb_http_alternates(writer: &mut impl Write, alternates: &[String]) -> Result<()> {
4107 for alternate in alternates {
4108 validate_dumb_http_alternate(alternate)?;
4109 writer.write_all(alternate.as_bytes())?;
4110 writer.write_all(b"\n")?;
4111 }
4112 Ok(())
4113}
4114
4115pub fn parse_dumb_http_packs(
4116 format: ObjectFormat,
4117 input: &[u8],
4118) -> Result<Vec<DumbHttpPackRecord>> {
4119 if input.is_empty() {
4120 return Ok(Vec::new());
4121 }
4122 input
4123 .split_inclusive(|byte| *byte == b'\n')
4124 .map(|line| parse_dumb_http_pack_record(format, line))
4125 .collect()
4126}
4127
4128pub fn encode_dumb_http_packs(records: &[DumbHttpPackRecord]) -> Result<Vec<u8>> {
4129 let mut out = Vec::new();
4130 for record in records {
4131 out.extend_from_slice(format!("P pack-{}.pack\n", record.hash).as_bytes());
4132 }
4133 Ok(out)
4134}
4135
4136pub fn read_dumb_http_packs(
4137 format: ObjectFormat,
4138 reader: &mut impl Read,
4139) -> Result<Vec<DumbHttpPackRecord>> {
4140 let mut input = Vec::new();
4141 reader.read_to_end(&mut input)?;
4142 parse_dumb_http_packs(format, &input)
4143}
4144
4145pub fn write_dumb_http_packs(
4146 writer: &mut impl Write,
4147 records: &[DumbHttpPackRecord],
4148) -> Result<()> {
4149 for record in records {
4150 writer.write_all(format!("P pack-{}.pack\n", record.hash).as_bytes())?;
4151 }
4152 Ok(())
4153}
4154
4155pub fn parse_upload_pack_request(
4156 format: ObjectFormat,
4157 frames: &[PktLineFrame],
4158) -> Result<Option<UploadPackRequest>> {
4159 if matches!(frames, [PktLineFrame::Flush]) {
4160 return Ok(None);
4161 }
4162
4163 let mut request = UploadPackRequest::default();
4164 let mut in_options = false;
4165 let mut saw_flush = false;
4166 for (idx, frame) in frames.iter().enumerate() {
4167 match frame {
4168 PktLineFrame::Data(payload) if !saw_flush => {
4169 let text = parse_protocol_v2_line_text("upload-pack request line", payload)?;
4170 if let Some(value) = text.strip_prefix("want ") {
4171 if in_options {
4172 return Err(GitError::InvalidFormat(
4173 "upload-pack request has want after options".into(),
4174 ));
4175 }
4176 let (oid, capabilities) = if request.wants.is_empty() {
4177 value
4178 .split_once(' ')
4179 .map_or((value, None), |(oid, caps)| (oid, Some(caps.as_bytes())))
4180 } else {
4181 if value.contains(' ') {
4182 return Err(GitError::InvalidFormat(
4183 "additional upload-pack want has capabilities".into(),
4184 ));
4185 }
4186 (value, None)
4187 };
4188 validate_protocol_v2_token("upload-pack want", oid)?;
4189 request.wants.push(ObjectId::from_hex(format, oid)?);
4190 if let Some(capabilities) = capabilities {
4191 request.capabilities = parse_capabilities(capabilities)?;
4192 }
4193 continue;
4194 }
4195
4196 if request.wants.is_empty() {
4197 return Err(GitError::InvalidFormat(
4198 "upload-pack request must start with want".into(),
4199 ));
4200 }
4201 in_options = true;
4202 if text.starts_with("shallow ") {
4203 request.shallow.push(parse_oid_argument(
4204 format,
4205 "upload-pack shallow",
4206 text,
4207 "shallow ",
4208 )?);
4209 } else if text.starts_with("deepen ") {
4210 if request.deepen.is_some() {
4211 return Err(GitError::InvalidFormat(
4212 "upload-pack request has duplicate deepen".into(),
4213 ));
4214 }
4215 request.deepen =
4216 Some(parse_u32_argument("upload-pack deepen", text, "deepen ")?);
4217 } else if text.starts_with("deepen-since ") {
4218 if request.deepen_since.is_some() {
4219 return Err(GitError::InvalidFormat(
4220 "upload-pack request has duplicate deepen-since".into(),
4221 ));
4222 }
4223 request.deepen_since = Some(parse_u64_argument(
4224 "upload-pack deepen-since",
4225 text,
4226 "deepen-since ",
4227 )?);
4228 } else if let Some(name) = text.strip_prefix("deepen-not ") {
4229 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4230 request.deepen_not.push(name.to_string());
4231 } else if let Some(filter) = text.strip_prefix("filter ") {
4232 if request.filter.is_some() {
4233 return Err(GitError::InvalidFormat(
4234 "upload-pack request has duplicate filter".into(),
4235 ));
4236 }
4237 validate_protocol_v2_token("upload-pack filter", filter)?;
4238 request.filter = Some(filter.to_string());
4239 } else {
4240 return Err(GitError::InvalidFormat(format!(
4241 "unsupported upload-pack request line {text}"
4242 )));
4243 }
4244 }
4245 PktLineFrame::Data(_) => {
4246 return Err(GitError::InvalidFormat(
4247 "upload-pack request has data after flush".into(),
4248 ));
4249 }
4250 PktLineFrame::Flush => {
4251 saw_flush = true;
4252 if idx + 1 != frames.len() {
4253 return Err(GitError::InvalidFormat(
4254 "upload-pack request has frames after flush".into(),
4255 ));
4256 }
4257 }
4258 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4259 return Err(GitError::InvalidFormat(
4260 "upload-pack request contains a non-flush control packet".into(),
4261 ));
4262 }
4263 }
4264 }
4265 if !saw_flush {
4266 return Err(GitError::InvalidFormat(
4267 "upload-pack request missing flush".into(),
4268 ));
4269 }
4270 if request.wants.is_empty() {
4271 return Err(GitError::InvalidFormat(
4272 "upload-pack request missing want".into(),
4273 ));
4274 }
4275 Ok(Some(request))
4276}
4277
4278pub fn encode_upload_pack_request(
4279 request: Option<&UploadPackRequest>,
4280) -> Result<Vec<PktLineFrame>> {
4281 let Some(request) = request else {
4282 return Ok(vec![PktLineFrame::Flush]);
4283 };
4284 if request.wants.is_empty() {
4285 return Err(GitError::InvalidFormat(
4286 "upload-pack request missing want".into(),
4287 ));
4288 }
4289
4290 let mut frames = Vec::new();
4291 for (idx, oid) in request.wants.iter().enumerate() {
4292 let mut line = format!("want {oid}");
4293 if idx == 0 && !request.capabilities.is_empty() {
4294 line.push(' ');
4295 line.push_str(
4296 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4297 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4298 );
4299 }
4300 frames.push(PktLineFrame::data(line_from_str(&line))?);
4301 }
4302 for oid in &request.shallow {
4303 frames.push(PktLineFrame::data(line_from_str(&format!(
4304 "shallow {oid}"
4305 )))?);
4306 }
4307 if let Some(deepen) = request.deepen {
4308 if deepen == 0 {
4309 return Err(GitError::InvalidFormat(
4310 "upload-pack deepen must be positive".into(),
4311 ));
4312 }
4313 frames.push(PktLineFrame::data(line_from_str(&format!(
4314 "deepen {deepen}"
4315 )))?);
4316 }
4317 if let Some(deepen_since) = request.deepen_since {
4318 frames.push(PktLineFrame::data(line_from_str(&format!(
4319 "deepen-since {deepen_since}"
4320 )))?);
4321 }
4322 for name in &request.deepen_not {
4323 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4324 frames.push(PktLineFrame::data(line_from_str(&format!(
4325 "deepen-not {name}"
4326 )))?);
4327 }
4328 if let Some(filter) = &request.filter {
4329 validate_protocol_v2_token("upload-pack filter", filter)?;
4330 frames.push(PktLineFrame::data(line_from_str(&format!(
4331 "filter {filter}"
4332 )))?);
4333 }
4334 frames.push(PktLineFrame::Flush);
4335 Ok(frames)
4336}
4337
4338pub fn read_upload_pack_request(
4339 format: ObjectFormat,
4340 reader: &mut impl Read,
4341) -> Result<Option<UploadPackRequest>> {
4342 let frames = read_pkt_line_frames_until_flush(reader)?;
4343 parse_upload_pack_request(format, &frames)
4344}
4345
4346pub fn write_upload_pack_request(
4347 writer: &mut impl Write,
4348 request: Option<&UploadPackRequest>,
4349) -> Result<()> {
4350 let Some(request) = request else {
4351 writer.write_all(b"0000")?;
4352 return Ok(());
4353 };
4354 if request.wants.is_empty() {
4355 return Err(GitError::InvalidFormat(
4356 "upload-pack request missing want".into(),
4357 ));
4358 }
4359
4360 for (idx, oid) in request.wants.iter().enumerate() {
4361 let mut line = format!("want {oid}");
4362 if idx == 0 && !request.capabilities.is_empty() {
4363 line.push(' ');
4364 line.push_str(
4365 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4366 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4367 );
4368 }
4369 write_pkt_line_payload(writer, &line_from_str(&line))?;
4370 }
4371 for oid in &request.shallow {
4372 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
4373 }
4374 if let Some(deepen) = request.deepen {
4375 if deepen == 0 {
4376 return Err(GitError::InvalidFormat(
4377 "upload-pack deepen must be positive".into(),
4378 ));
4379 }
4380 write_pkt_line_payload(writer, &line_from_str(&format!("deepen {deepen}")))?;
4381 }
4382 if let Some(deepen_since) = request.deepen_since {
4383 write_pkt_line_payload(
4384 writer,
4385 &line_from_str(&format!("deepen-since {deepen_since}")),
4386 )?;
4387 }
4388 for name in &request.deepen_not {
4389 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4390 write_pkt_line_payload(writer, &line_from_str(&format!("deepen-not {name}")))?;
4391 }
4392 if let Some(filter) = &request.filter {
4393 validate_protocol_v2_token("upload-pack filter", filter)?;
4394 write_pkt_line_payload(writer, &line_from_str(&format!("filter {filter}")))?;
4395 }
4396 writer.write_all(b"0000")?;
4397 Ok(())
4398}
4399
4400pub fn parse_upload_pack_features(capabilities: &[Capability]) -> Result<UploadPackFeatures> {
4401 let mut features = UploadPackFeatures::default();
4402 for capability in capabilities {
4403 match capability.name.as_str() {
4404 "multi_ack" => set_upload_pack_flag(&mut features.multi_ack, capability)?,
4405 "multi_ack_detailed" => {
4406 set_upload_pack_flag(&mut features.multi_ack_detailed, capability)?
4407 }
4408 "no-done" => set_upload_pack_flag(&mut features.no_done, capability)?,
4409 "thin-pack" => set_upload_pack_flag(&mut features.thin_pack, capability)?,
4410 "side-band" => set_upload_pack_flag(&mut features.side_band, capability)?,
4411 "side-band-64k" => set_upload_pack_flag(&mut features.side_band_64k, capability)?,
4412 "ofs-delta" => set_upload_pack_flag(&mut features.ofs_delta, capability)?,
4413 "shallow" => set_upload_pack_flag(&mut features.shallow, capability)?,
4414 "deepen-since" => set_upload_pack_flag(&mut features.deepen_since, capability)?,
4415 "deepen-not" => set_upload_pack_flag(&mut features.deepen_not, capability)?,
4416 "include-tag" => set_upload_pack_flag(&mut features.include_tag, capability)?,
4417 "no-progress" => set_upload_pack_flag(&mut features.no_progress, capability)?,
4418 "allow-tip-sha1-in-want" => {
4419 set_upload_pack_flag(&mut features.allow_tip_sha1_in_want, capability)?
4420 }
4421 "allow-reachable-sha1-in-want" => {
4422 set_upload_pack_flag(&mut features.allow_reachable_sha1_in_want, capability)?
4423 }
4424 "filter" => set_upload_pack_flag(&mut features.filter, capability)?,
4425 "agent" => {
4426 let Some(agent) = &capability.value else {
4427 return Err(GitError::InvalidFormat(
4428 "upload-pack agent capability is missing value".into(),
4429 ));
4430 };
4431 if features.agent.is_some() {
4432 return Err(GitError::InvalidFormat(
4433 "upload-pack has duplicate agent capability".into(),
4434 ));
4435 }
4436 validate_capability_field("upload-pack agent", agent)?;
4437 features.agent = Some(agent.clone());
4438 }
4439 "object-format" => {
4440 let Some(format) = &capability.value else {
4441 return Err(GitError::InvalidFormat(
4442 "upload-pack object-format capability is missing value".into(),
4443 ));
4444 };
4445 if features.object_format.is_some() {
4446 return Err(GitError::InvalidFormat(
4447 "upload-pack has duplicate object-format capability".into(),
4448 ));
4449 }
4450 validate_capability_field("upload-pack object-format", format)?;
4451 features.object_format = Some(format.parse()?);
4452 }
4453 "symref" => {
4454 let Some(symref) = &capability.value else {
4455 continue;
4456 };
4457 validate_capability_field("upload-pack symref", symref)?;
4458 features.symrefs.push(symref.clone());
4459 }
4460 _ => {
4461 encode_capabilities(std::slice::from_ref(capability))?;
4462 if features
4463 .unknown
4464 .iter()
4465 .any(|known| known.name == capability.name)
4466 {
4467 return Err(GitError::InvalidFormat(format!(
4468 "upload-pack has duplicate {} capability",
4469 capability.name
4470 )));
4471 }
4472 features.unknown.push(capability.clone());
4473 }
4474 }
4475 }
4476 Ok(features)
4477}
4478
4479pub fn encode_upload_pack_features(features: &UploadPackFeatures) -> Result<Vec<Capability>> {
4480 let mut capabilities = Vec::new();
4481 push_upload_pack_flag(&mut capabilities, "multi_ack", features.multi_ack);
4482 push_upload_pack_flag(
4483 &mut capabilities,
4484 "multi_ack_detailed",
4485 features.multi_ack_detailed,
4486 );
4487 push_upload_pack_flag(&mut capabilities, "no-done", features.no_done);
4488 push_upload_pack_flag(&mut capabilities, "thin-pack", features.thin_pack);
4489 push_upload_pack_flag(&mut capabilities, "side-band", features.side_band);
4490 push_upload_pack_flag(&mut capabilities, "side-band-64k", features.side_band_64k);
4491 push_upload_pack_flag(&mut capabilities, "ofs-delta", features.ofs_delta);
4492 push_upload_pack_flag(&mut capabilities, "shallow", features.shallow);
4493 push_upload_pack_flag(&mut capabilities, "deepen-since", features.deepen_since);
4494 push_upload_pack_flag(&mut capabilities, "deepen-not", features.deepen_not);
4495 push_upload_pack_flag(&mut capabilities, "include-tag", features.include_tag);
4496 push_upload_pack_flag(&mut capabilities, "no-progress", features.no_progress);
4497 push_upload_pack_flag(
4498 &mut capabilities,
4499 "allow-tip-sha1-in-want",
4500 features.allow_tip_sha1_in_want,
4501 );
4502 push_upload_pack_flag(
4503 &mut capabilities,
4504 "allow-reachable-sha1-in-want",
4505 features.allow_reachable_sha1_in_want,
4506 );
4507 push_upload_pack_flag(&mut capabilities, "filter", features.filter);
4508 if let Some(agent) = &features.agent {
4509 validate_capability_field("upload-pack agent", agent)?;
4510 capabilities.push(Capability {
4511 name: "agent".into(),
4512 value: Some(agent.clone()),
4513 });
4514 }
4515 if let Some(format) = features.object_format {
4516 capabilities.push(Capability {
4517 name: "object-format".into(),
4518 value: Some(format.name().into()),
4519 });
4520 }
4521 for symref in &features.symrefs {
4522 validate_capability_field("upload-pack symref", symref)?;
4523 capabilities.push(Capability {
4524 name: "symref".into(),
4525 value: Some(symref.clone()),
4526 });
4527 }
4528 for capability in &features.unknown {
4529 if is_known_upload_pack_capability(&capability.name) {
4530 return Err(GitError::InvalidFormat(format!(
4531 "upload-pack unknown capability duplicates known capability {}",
4532 capability.name
4533 )));
4534 }
4535 encode_capabilities(std::slice::from_ref(capability))?;
4536 capabilities.push(capability.clone());
4537 }
4538 Ok(capabilities)
4539}
4540
4541pub fn validate_upload_pack_request_features(
4542 features: &UploadPackFeatures,
4543 request: &UploadPackRequest,
4544) -> Result<()> {
4545 for capability in &request.capabilities {
4546 if is_upload_pack_flag_capability(&capability.name) {
4547 reject_capability_value("upload-pack request capability", capability)?;
4548 }
4549 match capability.name.as_str() {
4550 "multi_ack" if !features.multi_ack => {
4551 return Err(GitError::InvalidFormat(
4552 "upload-pack request uses multi_ack without advertised capability".into(),
4553 ));
4554 }
4555 "multi_ack_detailed" if !features.multi_ack_detailed => {
4556 return Err(GitError::InvalidFormat(
4557 "upload-pack request uses multi_ack_detailed without advertised capability"
4558 .into(),
4559 ));
4560 }
4561 "no-done" if !features.no_done => {
4562 return Err(GitError::InvalidFormat(
4563 "upload-pack request uses no-done without advertised capability".into(),
4564 ));
4565 }
4566 "thin-pack" if !features.thin_pack => {
4567 return Err(GitError::InvalidFormat(
4568 "upload-pack request uses thin-pack without advertised capability".into(),
4569 ));
4570 }
4571 "side-band" if !features.side_band => {
4572 return Err(GitError::InvalidFormat(
4573 "upload-pack request uses side-band without advertised capability".into(),
4574 ));
4575 }
4576 "side-band-64k" if !features.side_band_64k => {
4577 return Err(GitError::InvalidFormat(
4578 "upload-pack request uses side-band-64k without advertised capability".into(),
4579 ));
4580 }
4581 "ofs-delta" if !features.ofs_delta => {
4582 return Err(GitError::InvalidFormat(
4583 "upload-pack request uses ofs-delta without advertised capability".into(),
4584 ));
4585 }
4586 "include-tag" if !features.include_tag => {
4587 return Err(GitError::InvalidFormat(
4588 "upload-pack request uses include-tag without advertised capability".into(),
4589 ));
4590 }
4591 "no-progress" if !features.no_progress => {
4592 return Err(GitError::InvalidFormat(
4593 "upload-pack request uses no-progress without advertised capability".into(),
4594 ));
4595 }
4596 "allow-tip-sha1-in-want" if !features.allow_tip_sha1_in_want => {
4597 return Err(GitError::InvalidFormat(
4598 "upload-pack request uses allow-tip-sha1-in-want without advertised capability"
4599 .into(),
4600 ));
4601 }
4602 "allow-reachable-sha1-in-want" if !features.allow_reachable_sha1_in_want => {
4603 return Err(GitError::InvalidFormat(
4604 "upload-pack request uses allow-reachable-sha1-in-want without advertised capability"
4605 .into(),
4606 ));
4607 }
4608 "filter" if !features.filter => {
4609 return Err(GitError::InvalidFormat(
4610 "upload-pack request uses filter capability without advertised capability"
4611 .into(),
4612 ));
4613 }
4614 "agent" => {
4615 let Some(agent) = &capability.value else {
4616 return Err(GitError::InvalidFormat(
4617 "upload-pack request agent capability is missing value".into(),
4618 ));
4619 };
4620 validate_capability_field("upload-pack request agent", agent)?;
4621 }
4622 "object-format" => {
4623 let Some(format) = &capability.value else {
4624 return Err(GitError::InvalidFormat(
4625 "upload-pack request object-format capability is missing value".into(),
4626 ));
4627 };
4628 let requested_format: ObjectFormat = format.parse()?;
4629 if features.object_format != Some(requested_format) {
4630 return Err(GitError::InvalidFormat(
4631 "upload-pack request object-format was not advertised".into(),
4632 ));
4633 }
4634 }
4635 name if is_known_upload_pack_capability(name) => {}
4636 _ => {
4637 if !features
4638 .unknown
4639 .iter()
4640 .any(|advertised| advertised.name == capability.name)
4641 {
4642 return Err(GitError::InvalidFormat(format!(
4643 "upload-pack request uses unadvertised capability {}",
4644 capability.name
4645 )));
4646 }
4647 }
4648 }
4649 }
4650
4651 let sideband = request
4652 .capabilities
4653 .iter()
4654 .any(|capability| capability.name == "side-band");
4655 let sideband_64k = request
4656 .capabilities
4657 .iter()
4658 .any(|capability| capability.name == "side-band-64k");
4659 if sideband && sideband_64k {
4660 return Err(GitError::InvalidFormat(
4661 "upload-pack request must not request both side-band and side-band-64k".into(),
4662 ));
4663 }
4664
4665 if !features.shallow && (!request.shallow.is_empty() || request.deepen.is_some()) {
4666 return Err(GitError::InvalidFormat(
4667 "upload-pack request uses shallow/deepen without advertised shallow capability".into(),
4668 ));
4669 }
4670 if !features.deepen_since && request.deepen_since.is_some() {
4671 return Err(GitError::InvalidFormat(
4672 "upload-pack request uses deepen-since without advertised capability".into(),
4673 ));
4674 }
4675 if !features.deepen_not && !request.deepen_not.is_empty() {
4676 return Err(GitError::InvalidFormat(
4677 "upload-pack request uses deepen-not without advertised capability".into(),
4678 ));
4679 }
4680 if !features.filter && request.filter.is_some() {
4681 return Err(GitError::InvalidFormat(
4682 "upload-pack request uses filter without advertised capability".into(),
4683 ));
4684 }
4685 Ok(())
4686}
4687
4688pub fn build_upload_pack_raw_packfile_response<C, B>(
4689 features: &UploadPackFeatures,
4690 request: UploadPackRequest,
4691 haves: impl IntoIterator<Item = ObjectId>,
4692 mut contains_object: C,
4693 mut build_pack: B,
4694) -> Result<UploadPackRawPackfileResponse>
4695where
4696 C: FnMut(&ObjectId) -> Result<bool>,
4697 B: FnMut(Vec<ObjectId>, Vec<ObjectId>) -> Result<Option<Vec<u8>>>,
4698{
4699 validate_upload_pack_request_features(features, &request)?;
4700 for want in &request.wants {
4701 if !contains_object(want)? {
4702 return Err(GitError::InvalidObject(format!(
4703 "upload-pack requested missing object {want}"
4704 )));
4705 }
4706 }
4707 let known_haves = haves
4708 .into_iter()
4709 .filter_map(|oid| match contains_object(&oid) {
4710 Ok(true) => Some(Ok(oid)),
4711 Ok(false) => None,
4712 Err(err) => Some(Err(err)),
4713 })
4714 .collect::<Result<Vec<_>>>()?;
4715 let packfile = build_pack(request.wants, known_haves)?
4716 .ok_or_else(|| GitError::InvalidObject("upload-pack request produced empty pack".into()))?;
4717 Ok(UploadPackRawPackfileResponse {
4718 acknowledgments: vec![UploadPackAcknowledgment::Nak],
4719 packfile,
4720 })
4721}
4722
4723pub fn parse_upload_pack_shallow_update(
4724 format: ObjectFormat,
4725 frames: &[PktLineFrame],
4726) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4727 let mut entries = Vec::new();
4728 let mut saw_flush = false;
4729 for (idx, frame) in frames.iter().enumerate() {
4730 match frame {
4731 PktLineFrame::Data(payload) if !saw_flush => {
4732 entries.push(parse_fetch_shallow_info(format, payload)?);
4733 }
4734 PktLineFrame::Data(_) => {
4735 return Err(GitError::InvalidFormat(
4736 "upload-pack shallow update has data after flush".into(),
4737 ));
4738 }
4739 PktLineFrame::Flush => {
4740 saw_flush = true;
4741 if idx + 1 != frames.len() {
4742 return Err(GitError::InvalidFormat(
4743 "upload-pack shallow update has frames after flush".into(),
4744 ));
4745 }
4746 }
4747 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4748 return Err(GitError::InvalidFormat(
4749 "upload-pack shallow update contains a non-flush control packet".into(),
4750 ));
4751 }
4752 }
4753 }
4754 if !saw_flush {
4755 return Err(GitError::InvalidFormat(
4756 "upload-pack shallow update missing flush".into(),
4757 ));
4758 }
4759 Ok(entries)
4760}
4761
4762pub fn encode_upload_pack_shallow_update(
4763 entries: &[ProtocolV2FetchShallowInfo],
4764) -> Result<Vec<PktLineFrame>> {
4765 let mut frames = Vec::new();
4766 for entry in entries {
4767 let line = match entry {
4768 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4769 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4770 };
4771 frames.push(PktLineFrame::data(line_from_str(&line))?);
4772 }
4773 frames.push(PktLineFrame::Flush);
4774 Ok(frames)
4775}
4776
4777pub fn read_upload_pack_shallow_update(
4778 format: ObjectFormat,
4779 reader: &mut impl Read,
4780) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4781 let frames = read_pkt_line_frames_until_flush(reader)?;
4782 parse_upload_pack_shallow_update(format, &frames)
4783}
4784
4785pub fn write_upload_pack_shallow_update(
4786 writer: &mut impl Write,
4787 entries: &[ProtocolV2FetchShallowInfo],
4788) -> Result<()> {
4789 for entry in entries {
4790 let line = match entry {
4791 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4792 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4793 };
4794 write_pkt_line_payload(writer, &line_from_str(&line))?;
4795 }
4796 writer.write_all(b"0000")?;
4797 Ok(())
4798}
4799
4800pub fn parse_upload_pack_negotiation_request(
4801 format: ObjectFormat,
4802 frames: &[PktLineFrame],
4803) -> Result<UploadPackNegotiationRequest> {
4804 let mut request = UploadPackNegotiationRequest::default();
4805 let mut terminated = false;
4806 for (idx, frame) in frames.iter().enumerate() {
4807 match frame {
4808 PktLineFrame::Data(payload) if !terminated => {
4809 let text = parse_protocol_v2_line_text("upload-pack negotiation line", payload)?;
4810 if text == "done" {
4811 request.done = true;
4812 terminated = true;
4813 if idx + 1 != frames.len() {
4814 return Err(GitError::InvalidFormat(
4815 "upload-pack negotiation has frames after done".into(),
4816 ));
4817 }
4818 } else if text.starts_with("have ") {
4819 request.haves.push(parse_oid_argument(
4820 format,
4821 "upload-pack have",
4822 text,
4823 "have ",
4824 )?);
4825 } else {
4826 return Err(GitError::InvalidFormat(format!(
4827 "unsupported upload-pack negotiation line {text}"
4828 )));
4829 }
4830 }
4831 PktLineFrame::Data(_) => {
4832 return Err(GitError::InvalidFormat(
4833 "upload-pack negotiation has data after terminator".into(),
4834 ));
4835 }
4836 PktLineFrame::Flush => {
4837 terminated = true;
4838 if idx + 1 != frames.len() {
4839 return Err(GitError::InvalidFormat(
4840 "upload-pack negotiation has frames after flush".into(),
4841 ));
4842 }
4843 }
4844 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4845 return Err(GitError::InvalidFormat(
4846 "upload-pack negotiation contains a non-flush control packet".into(),
4847 ));
4848 }
4849 }
4850 }
4851 if !terminated {
4852 return Err(GitError::InvalidFormat(
4853 "upload-pack negotiation missing terminator".into(),
4854 ));
4855 }
4856 Ok(request)
4857}
4858
4859pub fn encode_upload_pack_negotiation_request(
4860 request: &UploadPackNegotiationRequest,
4861) -> Result<Vec<PktLineFrame>> {
4862 let mut frames = Vec::new();
4863 for oid in &request.haves {
4864 frames.push(PktLineFrame::data(line_from_str(&format!("have {oid}")))?);
4865 }
4866 if request.done {
4867 frames.push(PktLineFrame::data(line_from_str("done"))?);
4868 } else {
4869 frames.push(PktLineFrame::Flush);
4870 }
4871 Ok(frames)
4872}
4873
4874pub fn read_upload_pack_negotiation_request(
4875 format: ObjectFormat,
4876 reader: &mut impl Read,
4877) -> Result<UploadPackNegotiationRequest> {
4878 let mut frames = Vec::new();
4879 loop {
4880 let Some(frame) = read_pkt_line_frame(reader)? else {
4881 return Err(GitError::InvalidFormat(
4882 "pkt-line stream ended before upload-pack negotiation terminator".into(),
4883 ));
4884 };
4885 let done = match &frame {
4886 PktLineFrame::Flush => true,
4887 PktLineFrame::Data(payload) => trim_trailing_lf(payload) == b"done",
4888 _ => false,
4889 };
4890 frames.push(frame);
4891 if done {
4892 return parse_upload_pack_negotiation_request(format, &frames);
4893 }
4894 }
4895}
4896
4897pub fn write_upload_pack_negotiation_request(
4898 writer: &mut impl Write,
4899 request: &UploadPackNegotiationRequest,
4900) -> Result<()> {
4901 for oid in &request.haves {
4902 write_pkt_line_payload(writer, &line_from_str(&format!("have {oid}")))?;
4903 }
4904 if request.done {
4905 write_pkt_line_payload(writer, b"done\n")?;
4906 } else {
4907 writer.write_all(b"0000")?;
4908 }
4909 Ok(())
4910}
4911
4912pub fn parse_upload_pack_acknowledgment(
4913 format: ObjectFormat,
4914 payload: &[u8],
4915) -> Result<UploadPackAcknowledgment> {
4916 let text = parse_protocol_v2_line_text("upload-pack acknowledgment", payload)?;
4917 if text == "NAK" {
4918 return Ok(UploadPackAcknowledgment::Nak);
4919 }
4920 let Some(rest) = text.strip_prefix("ACK ") else {
4921 return Err(GitError::InvalidFormat(format!(
4922 "unsupported upload-pack acknowledgment {text}"
4923 )));
4924 };
4925 let mut fields = rest.split(' ');
4926 let oid = fields
4927 .next()
4928 .ok_or_else(|| GitError::InvalidFormat("upload-pack ACK missing object id".into()))?;
4929 validate_protocol_v2_token("upload-pack ACK", oid)?;
4930 let status = match fields.next() {
4931 None => None,
4932 Some("continue") => Some(UploadPackAckStatus::Continue),
4933 Some("common") => Some(UploadPackAckStatus::Common),
4934 Some("ready") => Some(UploadPackAckStatus::Ready),
4935 Some(other) => {
4936 return Err(GitError::InvalidFormat(format!(
4937 "unsupported upload-pack ACK status {other}"
4938 )));
4939 }
4940 };
4941 if fields.next().is_some() {
4942 return Err(GitError::InvalidFormat(
4943 "upload-pack ACK has too many fields".into(),
4944 ));
4945 }
4946 Ok(UploadPackAcknowledgment::Ack {
4947 oid: ObjectId::from_hex(format, oid)?,
4948 status,
4949 })
4950}
4951
4952pub fn encode_upload_pack_acknowledgment(
4953 acknowledgment: &UploadPackAcknowledgment,
4954) -> Result<Vec<u8>> {
4955 let line = match acknowledgment {
4956 UploadPackAcknowledgment::Nak => "NAK".to_string(),
4957 UploadPackAcknowledgment::Ack { oid, status } => {
4958 let mut line = format!("ACK {oid}");
4959 if let Some(status) = status {
4960 line.push(' ');
4961 line.push_str(match status {
4962 UploadPackAckStatus::Continue => "continue",
4963 UploadPackAckStatus::Common => "common",
4964 UploadPackAckStatus::Ready => "ready",
4965 });
4966 }
4967 line
4968 }
4969 };
4970 Ok(line_from_str(&line))
4971}
4972
4973pub fn read_upload_pack_acknowledgment(
4974 format: ObjectFormat,
4975 reader: &mut impl Read,
4976) -> Result<UploadPackAcknowledgment> {
4977 let Some(frame) = read_pkt_line_frame(reader)? else {
4978 return Err(GitError::InvalidFormat(
4979 "pkt-line stream ended before upload-pack acknowledgment".into(),
4980 ));
4981 };
4982 match frame {
4983 PktLineFrame::Data(payload) => parse_upload_pack_acknowledgment(format, &payload),
4984 _ => Err(GitError::InvalidFormat(
4985 "upload-pack acknowledgment must be a data packet".into(),
4986 )),
4987 }
4988}
4989
4990pub fn write_upload_pack_acknowledgment(
4991 writer: &mut impl Write,
4992 acknowledgment: &UploadPackAcknowledgment,
4993) -> Result<()> {
4994 write_pkt_line_payload(writer, &encode_upload_pack_acknowledgment(acknowledgment)?)
4995}
4996
4997pub fn parse_upload_pack_packfile_response(
4998 format: ObjectFormat,
4999 frames: &[PktLineFrame],
5000) -> Result<UploadPackPackfileResponse> {
5001 let mut response = UploadPackPackfileResponse::default();
5002 let mut in_sideband = false;
5003 let mut saw_flush = false;
5004 for (idx, frame) in frames.iter().enumerate() {
5005 match frame {
5006 PktLineFrame::Data(payload) if !saw_flush => {
5007 if !in_sideband
5008 && (trim_trailing_lf(payload) == b"NAK" || payload.starts_with(b"ACK "))
5009 {
5010 response
5011 .acknowledgments
5012 .push(parse_upload_pack_acknowledgment(format, payload)?);
5013 continue;
5014 }
5015 in_sideband = true;
5016 response.sideband.push(parse_sideband_packet(payload)?);
5017 }
5018 PktLineFrame::Data(_) => {
5019 return Err(GitError::InvalidFormat(
5020 "upload-pack packfile response has data after flush".into(),
5021 ));
5022 }
5023 PktLineFrame::Flush => {
5024 saw_flush = true;
5025 if idx + 1 != frames.len() {
5026 return Err(GitError::InvalidFormat(
5027 "upload-pack packfile response has frames after flush".into(),
5028 ));
5029 }
5030 }
5031 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5032 return Err(GitError::InvalidFormat(
5033 "upload-pack packfile response contains a non-flush control packet".into(),
5034 ));
5035 }
5036 }
5037 }
5038 if !saw_flush {
5039 return Err(GitError::InvalidFormat(
5040 "upload-pack packfile response missing flush".into(),
5041 ));
5042 }
5043 Ok(response)
5044}
5045
5046pub fn encode_upload_pack_packfile_response(
5047 response: &UploadPackPackfileResponse,
5048) -> Result<Vec<PktLineFrame>> {
5049 let mut frames = Vec::new();
5050 for acknowledgment in &response.acknowledgments {
5051 frames.push(PktLineFrame::data(encode_upload_pack_acknowledgment(
5052 acknowledgment,
5053 )?)?);
5054 }
5055 for packet in &response.sideband {
5056 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
5057 }
5058 frames.push(PktLineFrame::Flush);
5059 Ok(frames)
5060}
5061
5062pub fn read_upload_pack_packfile_response(
5063 format: ObjectFormat,
5064 reader: &mut impl Read,
5065) -> Result<UploadPackPackfileResponse> {
5066 let frames = read_pkt_line_frames_until_flush(reader)?;
5067 parse_upload_pack_packfile_response(format, &frames)
5068}
5069
5070pub fn write_upload_pack_packfile_response(
5071 writer: &mut impl Write,
5072 response: &UploadPackPackfileResponse,
5073) -> Result<()> {
5074 for acknowledgment in &response.acknowledgments {
5075 write_upload_pack_acknowledgment(writer, acknowledgment)?;
5076 }
5077 for packet in &response.sideband {
5078 write_sideband_packet(writer, packet)?;
5079 }
5080 writer.write_all(b"0000")?;
5081 Ok(())
5082}
5083
5084pub fn demux_upload_pack_packfile_response(
5085 response: &UploadPackPackfileResponse,
5086) -> Result<SideBandDemux> {
5087 demux_sideband_packets(&response.sideband)
5088}
5089
5090pub fn parse_upload_pack_raw_packfile_response(
5091 format: ObjectFormat,
5092 input: &[u8],
5093) -> Result<UploadPackRawPackfileResponse> {
5094 let mut response = UploadPackRawPackfileResponse::default();
5095 let mut offset = 0usize;
5096 while offset < input.len() {
5097 match PktLineFrame::parse(&input[offset..]) {
5098 Ok((PktLineFrame::Data(payload), consumed)) => {
5099 let trimmed = trim_trailing_lf(&payload);
5100 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
5101 response
5102 .acknowledgments
5103 .push(parse_upload_pack_acknowledgment(format, &payload)?);
5104 offset += consumed;
5105 continue;
5106 }
5107 return Err(GitError::InvalidFormat(
5108 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
5109 ));
5110 }
5111 Ok((PktLineFrame::Flush | PktLineFrame::Delimiter | PktLineFrame::ResponseEnd, _)) => {
5112 return Err(GitError::InvalidFormat(
5113 "upload-pack raw packfile response contains a control packet".into(),
5114 ));
5115 }
5116 Err(_) if input[offset..].starts_with(b"PACK") => break,
5117 Err(err) => return Err(err),
5118 }
5119 }
5120 response.packfile = input[offset..].to_vec();
5121 if response.packfile.is_empty() {
5122 return Err(GitError::InvalidFormat(
5123 "upload-pack raw packfile response missing packfile".into(),
5124 ));
5125 }
5126 if !response.packfile.starts_with(b"PACK") {
5127 return Err(GitError::InvalidFormat(
5128 "upload-pack raw packfile response packfile must start with PACK".into(),
5129 ));
5130 }
5131 Ok(response)
5132}
5133
5134pub fn encode_upload_pack_raw_packfile_response(
5135 response: &UploadPackRawPackfileResponse,
5136) -> Result<Vec<u8>> {
5137 if response.packfile.is_empty() {
5138 return Err(GitError::InvalidFormat(
5139 "upload-pack raw packfile response missing packfile".into(),
5140 ));
5141 }
5142 if !response.packfile.starts_with(b"PACK") {
5143 return Err(GitError::InvalidFormat(
5144 "upload-pack raw packfile response packfile must start with PACK".into(),
5145 ));
5146 }
5147 let mut out = Vec::new();
5148 for acknowledgment in &response.acknowledgments {
5149 write_pkt_line_payload(
5150 &mut out,
5151 &encode_upload_pack_acknowledgment(acknowledgment)?,
5152 )?;
5153 }
5154 out.extend_from_slice(&response.packfile);
5155 Ok(out)
5156}
5157
5158pub fn read_upload_pack_raw_packfile_response(
5159 format: ObjectFormat,
5160 reader: &mut impl Read,
5161) -> Result<UploadPackRawPackfileResponse> {
5162 let mut input = Vec::new();
5163 reader.read_to_end(&mut input)?;
5164 parse_upload_pack_raw_packfile_response(format, &input)
5165}
5166
5167pub fn read_upload_pack_raw_packfile_response_header(
5168 format: ObjectFormat,
5169 reader: &mut impl Read,
5170) -> Result<UploadPackRawPackfileResponseHeader> {
5171 let mut acknowledgments = Vec::new();
5172 loop {
5173 let mut header = [0u8; 4];
5174 reader.read_exact(&mut header)?;
5175 if &header == b"PACK" {
5176 return Ok(UploadPackRawPackfileResponseHeader {
5177 acknowledgments,
5178 pack_prefix: header.to_vec(),
5179 });
5180 }
5181 let len = parse_pkt_len(&header)?;
5182 let payload = match len {
5183 0..=2 => {
5184 return Err(GitError::InvalidFormat(
5185 "upload-pack raw packfile response contains a control packet".into(),
5186 ));
5187 }
5188 3 => {
5189 return Err(GitError::InvalidFormat(
5190 "reserved pkt-line length 0003".into(),
5191 ));
5192 }
5193 4..=PKT_LINE_MAX_LEN => {
5194 let mut payload = vec![0; len - 4];
5195 reader.read_exact(&mut payload)?;
5196 payload
5197 }
5198 _ => {
5199 return Err(GitError::InvalidFormat(format!(
5200 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
5201 )));
5202 }
5203 };
5204 trace_packet_read_payload(&payload);
5205 let trimmed = trim_trailing_lf(&payload);
5206 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
5207 acknowledgments.push(parse_upload_pack_acknowledgment(format, &payload)?);
5208 continue;
5209 }
5210 return Err(GitError::InvalidFormat(
5211 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
5212 ));
5213 }
5214}
5215
5216pub fn read_upload_pack_shallow_info_section(
5217 format: ObjectFormat,
5218 reader: &mut impl Read,
5219) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
5220 let mut entries = Vec::new();
5221 loop {
5222 let Some(frame) = read_pkt_line_frame(reader)? else {
5223 return Err(GitError::InvalidFormat(
5224 "upload-pack shallow-info section ended before flush".into(),
5225 ));
5226 };
5227 match frame {
5228 PktLineFrame::Data(payload) => {
5229 entries.push(parse_fetch_shallow_info(format, &payload)?)
5230 }
5231 PktLineFrame::Flush => return Ok(entries),
5232 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5233 return Err(GitError::InvalidFormat(
5234 "upload-pack shallow-info section contains a non-flush control packet".into(),
5235 ));
5236 }
5237 }
5238 }
5239}
5240
5241pub fn read_upload_pack_shallow_info_and_raw_packfile_response_header(
5242 format: ObjectFormat,
5243 reader: &mut impl Read,
5244) -> Result<(
5245 Vec<ProtocolV2FetchShallowInfo>,
5246 UploadPackRawPackfileResponseHeader,
5247)> {
5248 let shallow = read_upload_pack_shallow_info_section(format, reader)?;
5249 let raw = read_upload_pack_raw_packfile_response_header(format, reader)?;
5250 Ok((shallow, raw))
5251}
5252
5253pub fn write_upload_pack_raw_packfile_response(
5254 writer: &mut impl Write,
5255 response: &UploadPackRawPackfileResponse,
5256) -> Result<()> {
5257 if response.packfile.is_empty() {
5258 return Err(GitError::InvalidFormat(
5259 "upload-pack raw packfile response missing packfile".into(),
5260 ));
5261 }
5262 if !response.packfile.starts_with(b"PACK") {
5263 return Err(GitError::InvalidFormat(
5264 "upload-pack raw packfile response packfile must start with PACK".into(),
5265 ));
5266 }
5267 for acknowledgment in &response.acknowledgments {
5268 write_upload_pack_acknowledgment(writer, acknowledgment)?;
5269 }
5270 writer.write_all(&response.packfile)?;
5271 Ok(())
5272}
5273
5274pub fn parse_upload_pack_shallow_info_section(
5285 format: ObjectFormat,
5286 input: &[u8],
5287) -> Result<(Vec<ProtocolV2FetchShallowInfo>, usize)> {
5288 let mut entries = Vec::new();
5289 let mut offset = 0usize;
5290 loop {
5291 let (frame, consumed) = PktLineFrame::parse(&input[offset..])?;
5292 offset += consumed;
5293 match frame {
5294 PktLineFrame::Data(payload) => {
5295 entries.push(parse_fetch_shallow_info(format, &payload)?)
5296 }
5297 PktLineFrame::Flush => return Ok((entries, offset)),
5298 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5299 return Err(GitError::InvalidFormat(
5300 "upload-pack shallow-info section contains a non-flush control packet".into(),
5301 ));
5302 }
5303 }
5304 }
5305}
5306
5307pub fn parse_upload_pack_shallow_info_and_raw_packfile_response(
5318 format: ObjectFormat,
5319 input: &[u8],
5320) -> Result<(
5321 Vec<ProtocolV2FetchShallowInfo>,
5322 UploadPackRawPackfileResponse,
5323)> {
5324 let (shallow, consumed) = parse_upload_pack_shallow_info_section(format, input)?;
5325 let response = parse_upload_pack_raw_packfile_response(format, &input[consumed..])?;
5326 Ok((shallow, response))
5327}
5328
5329pub fn read_upload_pack_shallow_info_and_raw_packfile_response(
5337 format: ObjectFormat,
5338 reader: &mut impl Read,
5339) -> Result<(
5340 Vec<ProtocolV2FetchShallowInfo>,
5341 UploadPackRawPackfileResponse,
5342)> {
5343 let (shallow, header) =
5344 read_upload_pack_shallow_info_and_raw_packfile_response_header(format, reader)?;
5345 let mut packfile = header.pack_prefix;
5346 reader.read_to_end(&mut packfile)?;
5347 Ok((
5348 shallow,
5349 UploadPackRawPackfileResponse {
5350 acknowledgments: header.acknowledgments,
5351 packfile,
5352 },
5353 ))
5354}
5355
5356pub fn parse_receive_pack_request(
5357 format: ObjectFormat,
5358 frames: &[PktLineFrame],
5359) -> Result<ReceivePackRequest> {
5360 let mut request = ReceivePackRequest::default();
5361 let mut saw_command = false;
5362 let mut saw_flush = false;
5363 for (idx, frame) in frames.iter().enumerate() {
5364 match frame {
5365 PktLineFrame::Data(payload) if !saw_flush => {
5366 let payload = trim_trailing_lf(payload);
5367 if payload.is_empty() {
5368 return Err(GitError::InvalidFormat(
5369 "receive-pack request line is empty".into(),
5370 ));
5371 }
5372 if let Some(shallow) = payload.strip_prefix(b"shallow ") {
5373 if saw_command {
5374 return Err(GitError::InvalidFormat(
5375 "receive-pack request has shallow after commands".into(),
5376 ));
5377 }
5378 let shallow = std::str::from_utf8(shallow)
5379 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
5380 validate_protocol_v2_token("receive-pack shallow", shallow)?;
5381 request.shallow.push(ObjectId::from_hex(format, shallow)?);
5382 continue;
5383 }
5384
5385 let (command, capabilities) = match payload.iter().position(|byte| *byte == 0) {
5386 Some(nul) if !saw_command => (
5387 &payload[..nul],
5388 Some(parse_capabilities(&payload[nul + 1..])?),
5389 ),
5390 Some(_) => {
5391 return Err(GitError::InvalidFormat(
5392 "receive-pack capabilities must appear on the first command".into(),
5393 ));
5394 }
5395 None => (payload, None),
5396 };
5397 let command = parse_receive_pack_command(format, command)?;
5398 if let Some(capabilities) = capabilities {
5399 request.capabilities = capabilities;
5400 }
5401 request.commands.push(command);
5402 saw_command = true;
5403 }
5404 PktLineFrame::Data(_) => {
5405 return Err(GitError::InvalidFormat(
5406 "receive-pack request has data after flush".into(),
5407 ));
5408 }
5409 PktLineFrame::Flush => {
5410 saw_flush = true;
5411 if idx + 1 != frames.len() {
5412 return Err(GitError::InvalidFormat(
5413 "receive-pack request has frames after flush".into(),
5414 ));
5415 }
5416 }
5417 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5418 return Err(GitError::InvalidFormat(
5419 "receive-pack request contains a non-flush control packet".into(),
5420 ));
5421 }
5422 }
5423 }
5424 if !saw_flush {
5425 return Err(GitError::InvalidFormat(
5426 "receive-pack request missing flush".into(),
5427 ));
5428 }
5429 if !request.shallow.is_empty() && request.commands.is_empty() {
5430 return Err(GitError::InvalidFormat(
5431 "receive-pack request has shallow lines without commands".into(),
5432 ));
5433 }
5434 Ok(request)
5435}
5436
5437pub fn encode_receive_pack_request(request: &ReceivePackRequest) -> Result<Vec<PktLineFrame>> {
5438 if !request.shallow.is_empty() && request.commands.is_empty() {
5439 return Err(GitError::InvalidFormat(
5440 "receive-pack request has shallow lines without commands".into(),
5441 ));
5442 }
5443
5444 let mut frames = Vec::new();
5445 for oid in &request.shallow {
5446 frames.push(PktLineFrame::data(line_from_str(&format!(
5447 "shallow {oid}"
5448 )))?);
5449 }
5450 for (idx, command) in request.commands.iter().enumerate() {
5451 let mut payload = format_receive_pack_command(command)?;
5452 if idx == 0 && !request.capabilities.is_empty() {
5453 payload.push(0);
5454 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5455 }
5456 payload.push(b'\n');
5457 frames.push(PktLineFrame::data(payload)?);
5458 }
5459 frames.push(PktLineFrame::Flush);
5460 Ok(frames)
5461}
5462
5463pub fn read_receive_pack_request(
5464 format: ObjectFormat,
5465 reader: &mut impl Read,
5466) -> Result<ReceivePackRequest> {
5467 let frames = read_pkt_line_frames_until_flush(reader)?;
5468 parse_receive_pack_request(format, &frames)
5469}
5470
5471pub fn write_receive_pack_request(
5472 writer: &mut impl Write,
5473 request: &ReceivePackRequest,
5474) -> Result<()> {
5475 if !request.shallow.is_empty() && request.commands.is_empty() {
5476 return Err(GitError::InvalidFormat(
5477 "receive-pack request has shallow lines without commands".into(),
5478 ));
5479 }
5480
5481 for oid in &request.shallow {
5482 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
5483 }
5484 for (idx, command) in request.commands.iter().enumerate() {
5485 let mut payload = format_receive_pack_command(command)?;
5486 if idx == 0 && !request.capabilities.is_empty() {
5487 payload.push(0);
5488 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5489 }
5490 payload.push(b'\n');
5491 write_pkt_line_payload(writer, &payload)?;
5492 }
5493 writer.write_all(b"0000")?;
5494 Ok(())
5495}
5496
5497pub fn parse_receive_pack_push_request(
5498 format: ObjectFormat,
5499 input: &[u8],
5500 has_push_options: bool,
5501) -> Result<ReceivePackPushRequest> {
5502 let (command_frames, consumed) = parse_pkt_line_frames_until_flush_from(input)?;
5503 let commands = parse_receive_pack_request(format, &command_frames)?;
5504 let mut offset = consumed;
5505 let push_options = if has_push_options {
5506 let (push_option_frames, consumed) =
5507 parse_pkt_line_frames_until_flush_from(&input[offset..])?;
5508 offset += consumed;
5509 Some(parse_receive_pack_push_options(&push_option_frames)?)
5510 } else {
5511 None
5512 };
5513 Ok(ReceivePackPushRequest {
5514 commands,
5515 push_options,
5516 packfile: input[offset..].to_vec(),
5517 })
5518}
5519
5520pub fn encode_receive_pack_push_request(request: &ReceivePackPushRequest) -> Result<Vec<u8>> {
5521 let mut out = Vec::new();
5522 write_receive_pack_request(&mut out, &request.commands)?;
5523 if let Some(push_options) = &request.push_options {
5524 write_receive_pack_push_options(&mut out, push_options)?;
5525 }
5526 out.extend_from_slice(&request.packfile);
5527 Ok(out)
5528}
5529
5530pub fn read_receive_pack_push_request(
5531 format: ObjectFormat,
5532 reader: &mut impl Read,
5533 has_push_options: bool,
5534) -> Result<ReceivePackPushRequest> {
5535 let header = read_receive_pack_push_request_header(format, reader, has_push_options)?;
5536 let mut packfile = Vec::new();
5537 reader.read_to_end(&mut packfile)?;
5538 Ok(ReceivePackPushRequest {
5539 commands: header.commands,
5540 push_options: header.push_options,
5541 packfile,
5542 })
5543}
5544
5545pub fn read_receive_pack_push_request_header(
5546 format: ObjectFormat,
5547 reader: &mut impl Read,
5548 has_push_options: bool,
5549) -> Result<ReceivePackPushRequestHeader> {
5550 let commands = read_receive_pack_request(format, reader)?;
5551 let push_options = if has_push_options {
5552 Some(read_receive_pack_push_options(reader)?)
5553 } else {
5554 None
5555 };
5556 Ok(ReceivePackPushRequestHeader {
5557 commands,
5558 push_options,
5559 })
5560}
5561
5562pub fn write_receive_pack_push_request(
5563 writer: &mut impl Write,
5564 request: &ReceivePackPushRequest,
5565) -> Result<()> {
5566 write_receive_pack_push_request_header(
5567 writer,
5568 &ReceivePackPushRequestHeader {
5569 commands: request.commands.clone(),
5570 push_options: request.push_options.clone(),
5571 },
5572 )?;
5573 writer.write_all(&request.packfile)?;
5574 Ok(())
5575}
5576
5577pub fn write_receive_pack_push_request_header(
5578 writer: &mut impl Write,
5579 header: &ReceivePackPushRequestHeader,
5580) -> Result<()> {
5581 write_receive_pack_request(writer, &header.commands)?;
5582 if let Some(push_options) = &header.push_options {
5583 write_receive_pack_push_options(writer, push_options)?;
5584 }
5585 Ok(())
5586}
5587
5588pub fn parse_receive_pack_features(capabilities: &[Capability]) -> Result<ReceivePackFeatures> {
5589 let mut features = ReceivePackFeatures::default();
5590 for capability in capabilities {
5591 match capability.name.as_str() {
5592 "report-status" => {
5593 reject_capability_value("receive-pack report-status", capability)?;
5594 if features.report_status {
5595 return Err(GitError::InvalidFormat(
5596 "receive-pack has duplicate report-status capability".into(),
5597 ));
5598 }
5599 features.report_status = true;
5600 }
5601 "report-status-v2" => {
5602 reject_capability_value("receive-pack report-status-v2", capability)?;
5603 if features.report_status_v2 {
5604 return Err(GitError::InvalidFormat(
5605 "receive-pack has duplicate report-status-v2 capability".into(),
5606 ));
5607 }
5608 features.report_status_v2 = true;
5609 }
5610 "delete-refs" => {
5611 reject_capability_value("receive-pack delete-refs", capability)?;
5612 if features.delete_refs {
5613 return Err(GitError::InvalidFormat(
5614 "receive-pack has duplicate delete-refs capability".into(),
5615 ));
5616 }
5617 features.delete_refs = true;
5618 }
5619 "ofs-delta" => {
5620 reject_capability_value("receive-pack ofs-delta", capability)?;
5621 if features.ofs_delta {
5622 return Err(GitError::InvalidFormat(
5623 "receive-pack has duplicate ofs-delta capability".into(),
5624 ));
5625 }
5626 features.ofs_delta = true;
5627 }
5628 "atomic" => {
5629 reject_capability_value("receive-pack atomic", capability)?;
5630 if features.atomic {
5631 return Err(GitError::InvalidFormat(
5632 "receive-pack has duplicate atomic capability".into(),
5633 ));
5634 }
5635 features.atomic = true;
5636 }
5637 "push-options" => {
5638 reject_capability_value("receive-pack push-options", capability)?;
5639 if features.push_options {
5640 return Err(GitError::InvalidFormat(
5641 "receive-pack has duplicate push-options capability".into(),
5642 ));
5643 }
5644 features.push_options = true;
5645 }
5646 "side-band-64k" => {
5647 reject_capability_value("receive-pack side-band-64k", capability)?;
5648 if features.side_band_64k {
5649 return Err(GitError::InvalidFormat(
5650 "receive-pack has duplicate side-band-64k capability".into(),
5651 ));
5652 }
5653 features.side_band_64k = true;
5654 }
5655 "quiet" => {
5656 reject_capability_value("receive-pack quiet", capability)?;
5657 if features.quiet {
5658 return Err(GitError::InvalidFormat(
5659 "receive-pack has duplicate quiet capability".into(),
5660 ));
5661 }
5662 features.quiet = true;
5663 }
5664 "no-thin" => {
5665 reject_capability_value("receive-pack no-thin", capability)?;
5666 if features.no_thin {
5667 return Err(GitError::InvalidFormat(
5668 "receive-pack has duplicate no-thin capability".into(),
5669 ));
5670 }
5671 features.no_thin = true;
5672 }
5673 "agent" => {
5674 let Some(agent) = &capability.value else {
5675 return Err(GitError::InvalidFormat(
5676 "receive-pack agent capability is missing value".into(),
5677 ));
5678 };
5679 if features.agent.is_some() {
5680 return Err(GitError::InvalidFormat(
5681 "receive-pack has duplicate agent capability".into(),
5682 ));
5683 }
5684 validate_capability_field("receive-pack agent", agent)?;
5685 features.agent = Some(agent.clone());
5686 }
5687 "object-format" => {
5688 let Some(format) = &capability.value else {
5689 return Err(GitError::InvalidFormat(
5690 "receive-pack object-format capability is missing value".into(),
5691 ));
5692 };
5693 if features.object_format.is_some() {
5694 return Err(GitError::InvalidFormat(
5695 "receive-pack has duplicate object-format capability".into(),
5696 ));
5697 }
5698 validate_capability_field("receive-pack object-format", format)?;
5699 features.object_format = Some(format.parse()?);
5700 }
5701 _ => {
5702 encode_capabilities(std::slice::from_ref(capability))?;
5703 if features
5704 .unknown
5705 .iter()
5706 .any(|known| known.name == capability.name)
5707 {
5708 return Err(GitError::InvalidFormat(format!(
5709 "receive-pack has duplicate {} capability",
5710 capability.name
5711 )));
5712 }
5713 features.unknown.push(capability.clone());
5714 }
5715 }
5716 }
5717 Ok(features)
5718}
5719
5720pub fn encode_receive_pack_features(features: &ReceivePackFeatures) -> Result<Vec<Capability>> {
5721 let mut capabilities = Vec::new();
5722 if features.report_status {
5723 capabilities.push(Capability {
5724 name: "report-status".into(),
5725 value: None,
5726 });
5727 }
5728 if features.report_status_v2 {
5729 capabilities.push(Capability {
5730 name: "report-status-v2".into(),
5731 value: None,
5732 });
5733 }
5734 if features.delete_refs {
5735 capabilities.push(Capability {
5736 name: "delete-refs".into(),
5737 value: None,
5738 });
5739 }
5740 if features.ofs_delta {
5741 capabilities.push(Capability {
5742 name: "ofs-delta".into(),
5743 value: None,
5744 });
5745 }
5746 if features.atomic {
5747 capabilities.push(Capability {
5748 name: "atomic".into(),
5749 value: None,
5750 });
5751 }
5752 if features.push_options {
5753 capabilities.push(Capability {
5754 name: "push-options".into(),
5755 value: None,
5756 });
5757 }
5758 if features.side_band_64k {
5759 capabilities.push(Capability {
5760 name: "side-band-64k".into(),
5761 value: None,
5762 });
5763 }
5764 if features.quiet {
5765 capabilities.push(Capability {
5766 name: "quiet".into(),
5767 value: None,
5768 });
5769 }
5770 if features.no_thin {
5771 capabilities.push(Capability {
5772 name: "no-thin".into(),
5773 value: None,
5774 });
5775 }
5776 if let Some(agent) = &features.agent {
5777 validate_capability_field("receive-pack agent", agent)?;
5778 capabilities.push(Capability {
5779 name: "agent".into(),
5780 value: Some(agent.clone()),
5781 });
5782 }
5783 if let Some(format) = features.object_format {
5784 capabilities.push(Capability {
5785 name: "object-format".into(),
5786 value: Some(format.name().into()),
5787 });
5788 }
5789 for capability in &features.unknown {
5790 if is_known_receive_pack_capability(&capability.name) {
5791 return Err(GitError::InvalidFormat(format!(
5792 "receive-pack unknown capability duplicates known capability {}",
5793 capability.name
5794 )));
5795 }
5796 encode_capabilities(std::slice::from_ref(capability))?;
5797 capabilities.push(capability.clone());
5798 }
5799 Ok(capabilities)
5800}
5801
5802pub fn validate_receive_pack_push_request_features(
5803 features: &ReceivePackFeatures,
5804 request: &ReceivePackPushRequest,
5805) -> Result<()> {
5806 for capability in &request.commands.capabilities {
5807 if matches!(
5808 capability.name.as_str(),
5809 "report-status"
5810 | "report-status-v2"
5811 | "delete-refs"
5812 | "ofs-delta"
5813 | "atomic"
5814 | "push-options"
5815 | "side-band-64k"
5816 | "quiet"
5817 | "no-thin"
5818 ) {
5819 reject_capability_value("receive-pack request capability", capability)?;
5820 }
5821 match capability.name.as_str() {
5822 "report-status" if !features.report_status => {
5823 return Err(GitError::InvalidFormat(
5824 "receive-pack request uses report-status without advertised capability".into(),
5825 ));
5826 }
5827 "report-status-v2" if !features.report_status_v2 => {
5828 return Err(GitError::InvalidFormat(
5829 "receive-pack request uses report-status-v2 without advertised capability"
5830 .into(),
5831 ));
5832 }
5833 "delete-refs" if !features.delete_refs => {
5834 return Err(GitError::InvalidFormat(
5835 "receive-pack request uses delete-refs without advertised capability".into(),
5836 ));
5837 }
5838 "ofs-delta" if !features.ofs_delta => {
5839 return Err(GitError::InvalidFormat(
5840 "receive-pack request uses ofs-delta without advertised capability".into(),
5841 ));
5842 }
5843 "atomic" if !features.atomic => {
5844 return Err(GitError::InvalidFormat(
5845 "receive-pack request uses atomic without advertised capability".into(),
5846 ));
5847 }
5848 "push-options" if !features.push_options => {
5849 return Err(GitError::InvalidFormat(
5850 "receive-pack request uses push-options without advertised capability".into(),
5851 ));
5852 }
5853 "side-band-64k" if !features.side_band_64k => {
5854 return Err(GitError::InvalidFormat(
5855 "receive-pack request uses side-band-64k without advertised capability".into(),
5856 ));
5857 }
5858 "quiet" if !features.quiet => {
5859 return Err(GitError::InvalidFormat(
5860 "receive-pack request uses quiet without advertised capability".into(),
5861 ));
5862 }
5863 "no-thin" => {
5864 return Err(GitError::InvalidFormat(
5865 "receive-pack request must not request no-thin".into(),
5866 ));
5867 }
5868 "agent" => {
5869 validate_capability_field(
5870 "receive-pack request agent",
5871 capability.value.as_deref().unwrap_or_default(),
5872 )?;
5873 }
5874 "object-format" => {
5875 let Some(value) = &capability.value else {
5876 return Err(GitError::InvalidFormat(
5877 "receive-pack request object-format capability is missing value".into(),
5878 ));
5879 };
5880 let requested_format: ObjectFormat = value.parse()?;
5881 if features.object_format != Some(requested_format) {
5882 return Err(GitError::InvalidFormat(
5883 "receive-pack request object-format was not advertised".into(),
5884 ));
5885 }
5886 }
5887 name if is_known_receive_pack_capability(name) => {}
5888 _ => {
5889 if !features
5890 .unknown
5891 .iter()
5892 .any(|advertised| advertised.name == capability.name)
5893 {
5894 return Err(GitError::InvalidFormat(format!(
5895 "receive-pack request uses unadvertised capability {}",
5896 capability.name
5897 )));
5898 }
5899 }
5900 }
5901 }
5902
5903 let requested_push_options = request
5904 .commands
5905 .capabilities
5906 .iter()
5907 .any(|capability| capability.name == "push-options");
5908 match (requested_push_options, &request.push_options) {
5909 (true, Some(_)) => {}
5910 (true, None) => {
5911 return Err(GitError::InvalidFormat(
5912 "receive-pack request uses push-options without push-options section".into(),
5913 ));
5914 }
5915 (false, Some(_)) => {
5916 return Err(GitError::InvalidFormat(
5917 "receive-pack request has push-options section without requested capability".into(),
5918 ));
5919 }
5920 (false, None) => {}
5921 }
5922
5923 let has_delete = request
5924 .commands
5925 .commands
5926 .iter()
5927 .any(is_receive_pack_delete_command);
5928 if has_delete && !features.delete_refs {
5929 return Err(GitError::InvalidFormat(
5930 "receive-pack request deletes refs without advertised delete-refs capability".into(),
5931 ));
5932 }
5933
5934 let has_update_or_create = request
5935 .commands
5936 .commands
5937 .iter()
5938 .any(|command| !is_receive_pack_delete_command(command));
5939 if !has_update_or_create && !request.packfile.is_empty() {
5940 return Err(GitError::InvalidFormat(
5941 "receive-pack delete-only request must not include packfile".into(),
5942 ));
5943 }
5944 Ok(())
5945}
5946
5947pub fn apply_receive_pack_push_request<R, I, C, U, D>(
5948 features: &ReceivePackFeatures,
5949 request: &ReceivePackPushRequest,
5950 mut read_ref: R,
5951 mut install_pack: I,
5952 mut contains_object: C,
5953 mut apply_updates: U,
5954 mut delete_ref: D,
5955) -> Result<ReceivePackReportStatus>
5956where
5957 R: FnMut(&str) -> Result<Option<ObjectId>>,
5958 I: FnMut(&[u8]) -> Result<()>,
5959 C: FnMut(&ObjectId) -> Result<bool>,
5960 U: FnMut(&[ReceivePackCommand]) -> Result<()>,
5961 D: FnMut(&ReceivePackCommand) -> Result<()>,
5962{
5963 validate_receive_pack_push_request_features(features, request)?;
5964
5965 for command in request
5966 .commands
5967 .commands
5968 .iter()
5969 .filter(|command| is_receive_pack_delete_command(command))
5970 {
5971 if !command.old_id.is_null() && read_ref(&command.name)? != Some(command.old_id.clone()) {
5972 return Err(GitError::Transaction(format!(
5973 "expected ref {} to match",
5974 command.name
5975 )));
5976 }
5977 }
5978
5979 let updates = request
5980 .commands
5981 .commands
5982 .iter()
5983 .filter(|command| !is_receive_pack_delete_command(command))
5984 .cloned()
5985 .collect::<Vec<_>>();
5986 if !updates.is_empty() {
5987 if !request.packfile.is_empty() {
5988 install_pack(&request.packfile)?;
5989 }
5990 for command in &updates {
5991 if !contains_object(&command.new_id)? {
5992 return Err(GitError::InvalidObject(format!(
5993 "receive-pack packfile did not provide {}",
5994 command.new_id
5995 )));
5996 }
5997 }
5998 apply_updates(&updates)?;
5999 }
6000
6001 for command in request
6002 .commands
6003 .commands
6004 .iter()
6005 .filter(|command| is_receive_pack_delete_command(command))
6006 {
6007 delete_ref(command)?;
6008 }
6009
6010 Ok(ReceivePackReportStatus {
6011 unpack: ReceivePackUnpackStatus::Ok,
6012 commands: request
6013 .commands
6014 .commands
6015 .iter()
6016 .map(|command| ReceivePackCommandStatus::Ok {
6017 name: command.name.clone(),
6018 })
6019 .collect(),
6020 })
6021}
6022
6023pub fn parse_receive_pack_report_status(
6024 frames: &[PktLineFrame],
6025) -> Result<ReceivePackReportStatus> {
6026 let Some((first, rest)) = frames.split_first() else {
6027 return Err(GitError::InvalidFormat(
6028 "receive-pack report-status is empty".into(),
6029 ));
6030 };
6031 let PktLineFrame::Data(payload) = first else {
6032 return Err(GitError::InvalidFormat(
6033 "receive-pack report-status must start with unpack status".into(),
6034 ));
6035 };
6036 let unpack = parse_receive_pack_unpack_status(payload)?;
6037
6038 let mut commands = Vec::new();
6039 let mut saw_flush = false;
6040 for (idx, frame) in rest.iter().enumerate() {
6041 match frame {
6042 PktLineFrame::Data(payload) if !saw_flush => {
6043 commands.push(parse_receive_pack_command_status(payload)?);
6044 }
6045 PktLineFrame::Data(_) => {
6046 return Err(GitError::InvalidFormat(
6047 "receive-pack report-status has data after flush".into(),
6048 ));
6049 }
6050 PktLineFrame::Flush => {
6051 saw_flush = true;
6052 if idx + 1 != rest.len() {
6053 return Err(GitError::InvalidFormat(
6054 "receive-pack report-status has frames after flush".into(),
6055 ));
6056 }
6057 }
6058 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
6059 return Err(GitError::InvalidFormat(
6060 "receive-pack report-status contains a non-flush control packet".into(),
6061 ));
6062 }
6063 }
6064 }
6065 if !saw_flush {
6066 return Err(GitError::InvalidFormat(
6067 "receive-pack report-status missing flush".into(),
6068 ));
6069 }
6070 Ok(ReceivePackReportStatus { unpack, commands })
6071}
6072
6073pub fn encode_receive_pack_report_status(
6074 report: &ReceivePackReportStatus,
6075) -> Result<Vec<PktLineFrame>> {
6076 let mut frames = Vec::new();
6077 frames.push(PktLineFrame::data(line_from_str(
6078 &format_receive_pack_unpack_status(&report.unpack)?,
6079 ))?);
6080 for command in &report.commands {
6081 frames.push(PktLineFrame::data(line_from_str(
6082 &format_receive_pack_command_status(command)?,
6083 ))?);
6084 }
6085 frames.push(PktLineFrame::Flush);
6086 Ok(frames)
6087}
6088
6089pub fn read_receive_pack_report_status(reader: &mut impl Read) -> Result<ReceivePackReportStatus> {
6090 let frames = read_pkt_line_frames_until_flush(reader)?;
6091 parse_receive_pack_report_status(&frames)
6092}
6093
6094pub fn write_receive_pack_report_status(
6095 writer: &mut impl Write,
6096 report: &ReceivePackReportStatus,
6097) -> Result<()> {
6098 write_pkt_line_payload(
6099 writer,
6100 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
6101 )?;
6102 for command in &report.commands {
6103 write_pkt_line_payload(
6104 writer,
6105 &line_from_str(&format_receive_pack_command_status(command)?),
6106 )?;
6107 }
6108 writer.write_all(b"0000")?;
6109 Ok(())
6110}
6111
6112pub fn parse_receive_pack_report_status_v2(
6113 format: ObjectFormat,
6114 frames: &[PktLineFrame],
6115) -> Result<ReceivePackReportStatusV2> {
6116 let Some((first, rest)) = frames.split_first() else {
6117 return Err(GitError::InvalidFormat(
6118 "receive-pack report-status-v2 is empty".into(),
6119 ));
6120 };
6121 let PktLineFrame::Data(payload) = first else {
6122 return Err(GitError::InvalidFormat(
6123 "receive-pack report-status-v2 must start with unpack status".into(),
6124 ));
6125 };
6126 let unpack = parse_receive_pack_unpack_status(payload)?;
6127
6128 let mut commands = Vec::new();
6129 let mut saw_flush = false;
6130 for (idx, frame) in rest.iter().enumerate() {
6131 match frame {
6132 PktLineFrame::Data(payload) if !saw_flush => {
6133 let text =
6134 parse_protocol_v2_line_text("receive-pack report-status-v2 line", payload)?;
6135 if text.starts_with("option ") {
6136 let Some(ReceivePackCommandStatusV2::Ok { options, .. }) = commands.last_mut()
6137 else {
6138 return Err(GitError::InvalidFormat(
6139 "receive-pack report-status-v2 option without ok status".into(),
6140 ));
6141 };
6142 parse_receive_pack_report_status_v2_option(format, text, options)?;
6143 } else {
6144 commands.push(parse_receive_pack_command_status_v2(text)?);
6145 }
6146 }
6147 PktLineFrame::Data(_) => {
6148 return Err(GitError::InvalidFormat(
6149 "receive-pack report-status-v2 has data after flush".into(),
6150 ));
6151 }
6152 PktLineFrame::Flush => {
6153 saw_flush = true;
6154 if idx + 1 != rest.len() {
6155 return Err(GitError::InvalidFormat(
6156 "receive-pack report-status-v2 has frames after flush".into(),
6157 ));
6158 }
6159 }
6160 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
6161 return Err(GitError::InvalidFormat(
6162 "receive-pack report-status-v2 contains a non-flush control packet".into(),
6163 ));
6164 }
6165 }
6166 }
6167 if !saw_flush {
6168 return Err(GitError::InvalidFormat(
6169 "receive-pack report-status-v2 missing flush".into(),
6170 ));
6171 }
6172 Ok(ReceivePackReportStatusV2 { unpack, commands })
6173}
6174
6175pub fn encode_receive_pack_report_status_v2(
6176 report: &ReceivePackReportStatusV2,
6177) -> Result<Vec<PktLineFrame>> {
6178 let mut frames = Vec::new();
6179 frames.push(PktLineFrame::data(line_from_str(
6180 &format_receive_pack_unpack_status(&report.unpack)?,
6181 ))?);
6182 for command in &report.commands {
6183 frames.push(PktLineFrame::data(line_from_str(
6184 &format_receive_pack_command_status_v2(command)?,
6185 ))?);
6186 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
6187 for option in format_receive_pack_report_status_v2_options(options)? {
6188 frames.push(PktLineFrame::data(line_from_str(&option))?);
6189 }
6190 }
6191 }
6192 frames.push(PktLineFrame::Flush);
6193 Ok(frames)
6194}
6195
6196pub fn read_receive_pack_report_status_v2(
6197 format: ObjectFormat,
6198 reader: &mut impl Read,
6199) -> Result<ReceivePackReportStatusV2> {
6200 let frames = read_pkt_line_frames_until_flush(reader)?;
6201 parse_receive_pack_report_status_v2(format, &frames)
6202}
6203
6204pub fn write_receive_pack_report_status_v2(
6205 writer: &mut impl Write,
6206 report: &ReceivePackReportStatusV2,
6207) -> Result<()> {
6208 write_pkt_line_payload(
6209 writer,
6210 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
6211 )?;
6212 for command in &report.commands {
6213 write_pkt_line_payload(
6214 writer,
6215 &line_from_str(&format_receive_pack_command_status_v2(command)?),
6216 )?;
6217 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
6218 for option in format_receive_pack_report_status_v2_options(options)? {
6219 write_pkt_line_payload(writer, &line_from_str(&option))?;
6220 }
6221 }
6222 }
6223 writer.write_all(b"0000")?;
6224 Ok(())
6225}
6226
6227pub fn parse_receive_pack_push_options(frames: &[PktLineFrame]) -> Result<Vec<String>> {
6228 let mut options = Vec::new();
6229 let mut saw_flush = false;
6230 for (idx, frame) in frames.iter().enumerate() {
6231 match frame {
6232 PktLineFrame::Data(payload) if !saw_flush => {
6233 let option = trim_trailing_lf(payload);
6234 validate_receive_pack_push_option(option)?;
6235 options.push(
6236 std::str::from_utf8(option)
6237 .map_err(|err| GitError::InvalidFormat(err.to_string()))?
6238 .to_string(),
6239 );
6240 }
6241 PktLineFrame::Data(_) => {
6242 return Err(GitError::InvalidFormat(
6243 "receive-pack push-options has data after flush".into(),
6244 ));
6245 }
6246 PktLineFrame::Flush => {
6247 saw_flush = true;
6248 if idx + 1 != frames.len() {
6249 return Err(GitError::InvalidFormat(
6250 "receive-pack push-options has frames after flush".into(),
6251 ));
6252 }
6253 }
6254 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
6255 return Err(GitError::InvalidFormat(
6256 "receive-pack push-options contains a non-flush control packet".into(),
6257 ));
6258 }
6259 }
6260 }
6261 if !saw_flush {
6262 return Err(GitError::InvalidFormat(
6263 "receive-pack push-options missing flush".into(),
6264 ));
6265 }
6266 Ok(options)
6267}
6268
6269pub fn encode_receive_pack_push_options(options: &[String]) -> Result<Vec<PktLineFrame>> {
6270 let mut frames = Vec::new();
6271 for option in options {
6272 validate_receive_pack_push_option(option.as_bytes())?;
6273 let mut payload = option.as_bytes().to_vec();
6274 payload.push(b'\n');
6275 frames.push(PktLineFrame::data(payload)?);
6276 }
6277 frames.push(PktLineFrame::Flush);
6278 Ok(frames)
6279}
6280
6281pub fn read_receive_pack_push_options(reader: &mut impl Read) -> Result<Vec<String>> {
6282 let frames = read_pkt_line_frames_until_flush(reader)?;
6283 parse_receive_pack_push_options(&frames)
6284}
6285
6286pub fn write_receive_pack_push_options(writer: &mut impl Write, options: &[String]) -> Result<()> {
6287 for option in options {
6288 validate_receive_pack_push_option(option.as_bytes())?;
6289 let mut payload = option.as_bytes().to_vec();
6290 payload.push(b'\n');
6291 write_pkt_line_payload(writer, &payload)?;
6292 }
6293 writer.write_all(b"0000")?;
6294 Ok(())
6295}
6296
6297fn parse_receive_pack_command(format: ObjectFormat, payload: &[u8]) -> Result<ReceivePackCommand> {
6298 let text =
6299 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6300 let mut fields = text.split(' ');
6301 let old_id = fields
6302 .next()
6303 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing old id".into()))?;
6304 let new_id = fields
6305 .next()
6306 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing new id".into()))?;
6307 let name = fields
6308 .next()
6309 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing ref name".into()))?;
6310 if fields.next().is_some() {
6311 return Err(GitError::InvalidFormat(
6312 "receive-pack command has too many fields".into(),
6313 ));
6314 }
6315 validate_protocol_v2_token("receive-pack old id", old_id)?;
6316 validate_protocol_v2_token("receive-pack new id", new_id)?;
6317 validate_protocol_v2_token("receive-pack ref name", name)?;
6318 Ok(ReceivePackCommand {
6319 old_id: ObjectId::from_hex(format, old_id)?,
6320 new_id: ObjectId::from_hex(format, new_id)?,
6321 name: name.to_string(),
6322 })
6323}
6324
6325fn format_receive_pack_command(command: &ReceivePackCommand) -> Result<Vec<u8>> {
6326 if command.old_id.format() != command.new_id.format() {
6327 return Err(GitError::InvalidObjectId(
6328 "receive-pack command object formats do not match".into(),
6329 ));
6330 }
6331 validate_protocol_v2_token("receive-pack ref name", &command.name)?;
6332 Ok(format!("{} {} {}", command.old_id, command.new_id, command.name).into_bytes())
6333}
6334
6335fn set_upload_pack_flag(value: &mut bool, capability: &Capability) -> Result<()> {
6336 reject_capability_value("upload-pack capability", capability)?;
6337 if *value {
6338 return Err(GitError::InvalidFormat(format!(
6339 "upload-pack has duplicate {} capability",
6340 capability.name
6341 )));
6342 }
6343 *value = true;
6344 Ok(())
6345}
6346
6347fn push_upload_pack_flag(capabilities: &mut Vec<Capability>, name: &str, enabled: bool) {
6348 if enabled {
6349 capabilities.push(Capability {
6350 name: name.into(),
6351 value: None,
6352 });
6353 }
6354}
6355
6356fn is_known_upload_pack_capability(name: &str) -> bool {
6357 matches!(
6358 name,
6359 "multi_ack"
6360 | "multi_ack_detailed"
6361 | "no-done"
6362 | "thin-pack"
6363 | "side-band"
6364 | "side-band-64k"
6365 | "ofs-delta"
6366 | "shallow"
6367 | "deepen-since"
6368 | "deepen-not"
6369 | "include-tag"
6370 | "no-progress"
6371 | "allow-tip-sha1-in-want"
6372 | "allow-reachable-sha1-in-want"
6373 | "filter"
6374 | "agent"
6375 | "object-format"
6376 | "symref"
6377 )
6378}
6379
6380fn is_upload_pack_flag_capability(name: &str) -> bool {
6381 matches!(
6382 name,
6383 "multi_ack"
6384 | "multi_ack_detailed"
6385 | "no-done"
6386 | "thin-pack"
6387 | "side-band"
6388 | "side-band-64k"
6389 | "ofs-delta"
6390 | "shallow"
6391 | "deepen-since"
6392 | "deepen-not"
6393 | "include-tag"
6394 | "no-progress"
6395 | "allow-tip-sha1-in-want"
6396 | "allow-reachable-sha1-in-want"
6397 | "filter"
6398 )
6399}
6400
6401fn reject_capability_value(label: &str, capability: &Capability) -> Result<()> {
6402 if capability.value.is_some() {
6403 return Err(GitError::InvalidFormat(format!(
6404 "{label} must not have value"
6405 )));
6406 }
6407 Ok(())
6408}
6409
6410fn is_known_receive_pack_capability(name: &str) -> bool {
6411 matches!(
6412 name,
6413 "report-status"
6414 | "report-status-v2"
6415 | "delete-refs"
6416 | "ofs-delta"
6417 | "atomic"
6418 | "push-options"
6419 | "side-band-64k"
6420 | "quiet"
6421 | "no-thin"
6422 | "agent"
6423 | "object-format"
6424 )
6425}
6426
6427fn is_receive_pack_delete_command(command: &ReceivePackCommand) -> bool {
6428 command.new_id.is_null()
6429}
6430
6431fn parse_receive_pack_unpack_status(payload: &[u8]) -> Result<ReceivePackUnpackStatus> {
6432 let text = parse_protocol_v2_line_text("receive-pack unpack status", payload)?;
6433 if text == "unpack ok" {
6434 return Ok(ReceivePackUnpackStatus::Ok);
6435 }
6436 let Some(message) = text.strip_prefix("unpack ") else {
6437 return Err(GitError::InvalidFormat(format!(
6438 "unsupported receive-pack unpack status {text}"
6439 )));
6440 };
6441 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6442 Ok(ReceivePackUnpackStatus::Error(message.to_string()))
6443}
6444
6445fn format_receive_pack_unpack_status(status: &ReceivePackUnpackStatus) -> Result<String> {
6446 match status {
6447 ReceivePackUnpackStatus::Ok => Ok("unpack ok".into()),
6448 ReceivePackUnpackStatus::Error(message) => {
6449 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6450 Ok(format!("unpack {message}"))
6451 }
6452 }
6453}
6454
6455fn parse_receive_pack_command_status(payload: &[u8]) -> Result<ReceivePackCommandStatus> {
6456 let text = parse_protocol_v2_line_text("receive-pack command status", payload)?;
6457 if let Some(name) = text.strip_prefix("ok ") {
6458 validate_protocol_v2_token("receive-pack status ref name", name)?;
6459 return Ok(ReceivePackCommandStatus::Ok {
6460 name: name.to_string(),
6461 });
6462 }
6463 let Some(rest) = text.strip_prefix("ng ") else {
6464 return Err(GitError::InvalidFormat(format!(
6465 "unsupported receive-pack command status {text}"
6466 )));
6467 };
6468 let (name, message) = rest
6469 .split_once(' ')
6470 .ok_or_else(|| GitError::InvalidFormat("receive-pack ng status missing message".into()))?;
6471 validate_protocol_v2_token("receive-pack status ref name", name)?;
6472 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6473 Ok(ReceivePackCommandStatus::Ng {
6474 name: name.to_string(),
6475 message: message.to_string(),
6476 })
6477}
6478
6479fn format_receive_pack_command_status(status: &ReceivePackCommandStatus) -> Result<String> {
6480 match status {
6481 ReceivePackCommandStatus::Ok { name } => {
6482 validate_protocol_v2_token("receive-pack status ref name", name)?;
6483 Ok(format!("ok {name}"))
6484 }
6485 ReceivePackCommandStatus::Ng { name, message } => {
6486 validate_protocol_v2_token("receive-pack status ref name", name)?;
6487 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6488 Ok(format!("ng {name} {message}"))
6489 }
6490 }
6491}
6492
6493fn parse_receive_pack_command_status_v2(text: &str) -> Result<ReceivePackCommandStatusV2> {
6494 if let Some(name) = text.strip_prefix("ok ") {
6495 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6496 return Ok(ReceivePackCommandStatusV2::Ok {
6497 name: name.to_string(),
6498 options: ReceivePackCommandStatusV2Options::default(),
6499 });
6500 }
6501 let Some(rest) = text.strip_prefix("ng ") else {
6502 return Err(GitError::InvalidFormat(format!(
6503 "unsupported receive-pack report-status-v2 line {text}"
6504 )));
6505 };
6506 let (name, message) = rest.split_once(' ').ok_or_else(|| {
6507 GitError::InvalidFormat("receive-pack status-v2 ng status missing message".into())
6508 })?;
6509 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6510 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6511 Ok(ReceivePackCommandStatusV2::Ng {
6512 name: name.to_string(),
6513 message: message.to_string(),
6514 })
6515}
6516
6517fn format_receive_pack_command_status_v2(status: &ReceivePackCommandStatusV2) -> Result<String> {
6518 match status {
6519 ReceivePackCommandStatusV2::Ok { name, .. } => {
6520 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6521 Ok(format!("ok {name}"))
6522 }
6523 ReceivePackCommandStatusV2::Ng { name, message } => {
6524 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6525 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6526 Ok(format!("ng {name} {message}"))
6527 }
6528 }
6529}
6530
6531fn parse_receive_pack_report_status_v2_option(
6532 format: ObjectFormat,
6533 text: &str,
6534 options: &mut ReceivePackCommandStatusV2Options,
6535) -> Result<()> {
6536 if let Some(refname) = text.strip_prefix("option refname ") {
6537 if options.refname.is_some() {
6538 return Err(GitError::InvalidFormat(
6539 "receive-pack report-status-v2 has duplicate refname option".into(),
6540 ));
6541 }
6542 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6543 options.refname = Some(refname.to_string());
6544 } else if let Some(old_oid) = text.strip_prefix("option old-oid ") {
6545 if options.old_oid.is_some() {
6546 return Err(GitError::InvalidFormat(
6547 "receive-pack report-status-v2 has duplicate old-oid option".into(),
6548 ));
6549 }
6550 validate_protocol_v2_token("receive-pack status-v2 option old-oid", old_oid)?;
6551 options.old_oid = Some(ObjectId::from_hex(format, old_oid)?);
6552 } else if let Some(new_oid) = text.strip_prefix("option new-oid ") {
6553 if options.new_oid.is_some() {
6554 return Err(GitError::InvalidFormat(
6555 "receive-pack report-status-v2 has duplicate new-oid option".into(),
6556 ));
6557 }
6558 validate_protocol_v2_token("receive-pack status-v2 option new-oid", new_oid)?;
6559 options.new_oid = Some(ObjectId::from_hex(format, new_oid)?);
6560 } else if text == "option forced-update" {
6561 if options.forced_update {
6562 return Err(GitError::InvalidFormat(
6563 "receive-pack report-status-v2 has duplicate forced-update option".into(),
6564 ));
6565 }
6566 options.forced_update = true;
6567 } else {
6568 return Err(GitError::InvalidFormat(format!(
6569 "unsupported receive-pack report-status-v2 option {text}"
6570 )));
6571 }
6572 Ok(())
6573}
6574
6575fn format_receive_pack_report_status_v2_options(
6576 options: &ReceivePackCommandStatusV2Options,
6577) -> Result<Vec<String>> {
6578 let mut out = Vec::new();
6579 if let Some(refname) = &options.refname {
6580 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6581 out.push(format!("option refname {refname}"));
6582 }
6583 if let Some(old_oid) = &options.old_oid {
6584 out.push(format!("option old-oid {old_oid}"));
6585 }
6586 if let Some(new_oid) = &options.new_oid {
6587 out.push(format!("option new-oid {new_oid}"));
6588 }
6589 if options.forced_update {
6590 out.push("option forced-update".into());
6591 }
6592 Ok(out)
6593}
6594
6595fn validate_receive_pack_status_message(label: &str, message: &str) -> Result<()> {
6596 if message.is_empty() {
6597 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6598 }
6599 if message
6600 .bytes()
6601 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6602 {
6603 return Err(GitError::InvalidFormat(format!(
6604 "{label} contains a delimiter byte"
6605 )));
6606 }
6607 Ok(())
6608}
6609
6610fn validate_receive_pack_push_option(option: &[u8]) -> Result<()> {
6611 if option.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6612 return Err(GitError::InvalidFormat(
6613 "receive-pack push-option contains a delimiter byte".into(),
6614 ));
6615 }
6616 Ok(())
6617}
6618
6619fn validate_protocol_error_message(message: &str) -> Result<()> {
6620 if message.is_empty() {
6621 return Err(GitError::InvalidFormat(
6622 "protocol error message is empty".into(),
6623 ));
6624 }
6625 if message
6626 .bytes()
6627 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6628 {
6629 return Err(GitError::InvalidFormat(
6630 "protocol error message contains a delimiter byte".into(),
6631 ));
6632 }
6633 Ok(())
6634}
6635
6636fn parse_capability_token(token: &str) -> Result<Capability> {
6637 if token.is_empty() {
6638 return Err(GitError::InvalidFormat("empty capability token".into()));
6639 }
6640 let (name, value) = token
6641 .split_once('=')
6642 .map_or((token, None), |(name, value)| (name, Some(value)));
6643 validate_capability_field("capability name", name)?;
6644 if let Some(value) = value {
6645 validate_capability_field("capability value", value)?;
6646 }
6647 Ok(Capability {
6648 name: name.to_string(),
6649 value: value.map(str::to_string),
6650 })
6651}
6652
6653fn parse_protocol_v2_capability_line(payload: &[u8]) -> Result<Capability> {
6654 let payload = trim_trailing_lf(payload);
6655 if payload.is_empty() {
6656 return Err(GitError::InvalidFormat(
6657 "empty protocol v2 capability line".into(),
6658 ));
6659 }
6660 let text =
6661 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6662 let (name, value) = text
6663 .split_once('=')
6664 .map_or((text, None), |(name, value)| (name, Some(value)));
6665 validate_capability_name(name)?;
6666 if let Some(value) = value {
6667 validate_protocol_v2_capability_value(value)?;
6668 }
6669 Ok(Capability {
6670 name: name.to_string(),
6671 value: value.map(str::to_string),
6672 })
6673}
6674
6675fn parse_protocol_v2_command_line(payload: &[u8]) -> Result<String> {
6676 let payload = trim_trailing_lf(payload);
6677 let text =
6678 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6679 let Some(command) = text.strip_prefix("command=") else {
6680 return Err(GitError::InvalidFormat(
6681 "protocol v2 command request missing command prefix".into(),
6682 ));
6683 };
6684 validate_capability_name(command)?;
6685 Ok(command.to_string())
6686}
6687
6688fn parse_protocol_v2_ls_refs_line(
6689 format: ObjectFormat,
6690 payload: &[u8],
6691) -> Result<ProtocolV2LsRefsRecord> {
6692 let payload = trim_trailing_lf(payload);
6693 if payload.is_empty() {
6694 return Err(GitError::InvalidFormat(
6695 "ls-refs response line is empty".into(),
6696 ));
6697 }
6698 let text =
6699 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6700 let mut fields = text.split(' ');
6701 let first = fields
6702 .next()
6703 .ok_or_else(|| GitError::InvalidFormat("ls-refs response line is empty".into()))?;
6704 if first == "unborn" {
6705 let name = fields
6706 .next()
6707 .ok_or_else(|| GitError::InvalidFormat("ls-refs unborn line is missing name".into()))?;
6708 validate_protocol_v2_token("ls-refs ref name", name)?;
6709 let (symref_target, attributes) = parse_protocol_v2_ls_refs_attributes(format, fields)?;
6710 return Ok(ProtocolV2LsRefsRecord::Unborn {
6711 name: name.to_string(),
6712 symref_target,
6713 attributes,
6714 });
6715 }
6716
6717 let oid = ObjectId::from_hex(format, first)?;
6718 let name = fields
6719 .next()
6720 .ok_or_else(|| GitError::InvalidFormat("ls-refs ref line is missing name".into()))?;
6721 validate_protocol_v2_token("ls-refs ref name", name)?;
6722 let (peeled, symref_target, attributes) =
6723 parse_protocol_v2_ls_refs_ref_attributes(format, fields)?;
6724 Ok(ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
6725 oid,
6726 name: name.to_string(),
6727 peeled,
6728 symref_target,
6729 attributes,
6730 }))
6731}
6732
6733fn parse_protocol_v2_ls_refs_ref_attributes<'a>(
6734 format: ObjectFormat,
6735 fields: impl Iterator<Item = &'a str>,
6736) -> Result<(Option<ObjectId>, Option<String>, Vec<String>)> {
6737 let mut peeled = None;
6738 let (symref_target, attributes) =
6739 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6740 if let Some(value) = attr.strip_prefix("peeled:") {
6741 if peeled.is_some() {
6742 return Err(GitError::InvalidFormat(
6743 "ls-refs response has duplicate peeled attribute".into(),
6744 ));
6745 }
6746 peeled = Some(ObjectId::from_hex(format, value)?);
6747 return Ok(true);
6748 }
6749 Ok(false)
6750 })?;
6751 Ok((peeled, symref_target, attributes))
6752}
6753
6754fn parse_protocol_v2_ls_refs_attributes<'a>(
6755 format: ObjectFormat,
6756 fields: impl Iterator<Item = &'a str>,
6757) -> Result<(Option<String>, Vec<String>)> {
6758 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6759 if attr.starts_with("peeled:") {
6760 return Err(GitError::InvalidFormat(
6761 "ls-refs unborn line has peeled attribute".into(),
6762 ));
6763 }
6764 Ok(false)
6765 })
6766}
6767
6768fn parse_protocol_v2_ls_refs_attributes_with<'a, F>(
6769 _format: ObjectFormat,
6770 fields: impl Iterator<Item = &'a str>,
6771 mut handle_known: F,
6772) -> Result<(Option<String>, Vec<String>)>
6773where
6774 F: FnMut(&str) -> Result<bool>,
6775{
6776 let mut symref_target = None;
6777 let mut attributes = Vec::new();
6778 for attr in fields {
6779 validate_protocol_v2_token("ls-refs attribute", attr)?;
6780 if let Some(value) = attr.strip_prefix("symref-target:") {
6781 if symref_target.is_some() {
6782 return Err(GitError::InvalidFormat(
6783 "ls-refs response has duplicate symref-target attribute".into(),
6784 ));
6785 }
6786 validate_protocol_v2_token("ls-refs symref-target", value)?;
6787 symref_target = Some(value.to_string());
6788 } else if !handle_known(attr)? {
6789 attributes.push(attr.to_string());
6790 }
6791 }
6792 Ok((symref_target, attributes))
6793}
6794
6795fn format_protocol_v2_ls_refs_record(record: &ProtocolV2LsRefsRecord) -> Result<String> {
6796 let mut out = String::new();
6797 match record {
6798 ProtocolV2LsRefsRecord::Ref(reference) => {
6799 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
6800 out.push_str(&reference.oid.to_string());
6801 out.push(' ');
6802 out.push_str(&reference.name);
6803 if let Some(peeled) = &reference.peeled {
6804 if peeled.format() != reference.oid.format() {
6805 return Err(GitError::InvalidObjectId(
6806 "ls-refs peeled object format does not match ref object format".into(),
6807 ));
6808 }
6809 out.push(' ');
6810 out.push_str("peeled:");
6811 out.push_str(&peeled.to_string());
6812 }
6813 if let Some(target) = &reference.symref_target {
6814 validate_protocol_v2_token("ls-refs symref-target", target)?;
6815 out.push(' ');
6816 out.push_str("symref-target:");
6817 out.push_str(target);
6818 }
6819 append_protocol_v2_ls_refs_attributes(&mut out, &reference.attributes)?;
6820 }
6821 ProtocolV2LsRefsRecord::Unborn {
6822 name,
6823 symref_target,
6824 attributes,
6825 } => {
6826 validate_protocol_v2_token("ls-refs ref name", name)?;
6827 out.push_str("unborn ");
6828 out.push_str(name);
6829 if let Some(target) = symref_target {
6830 validate_protocol_v2_token("ls-refs symref-target", target)?;
6831 out.push(' ');
6832 out.push_str("symref-target:");
6833 out.push_str(target);
6834 }
6835 append_protocol_v2_ls_refs_attributes(&mut out, attributes)?;
6836 }
6837 }
6838 Ok(out)
6839}
6840
6841fn append_protocol_v2_ls_refs_attributes(out: &mut String, attributes: &[String]) -> Result<()> {
6842 for attr in attributes {
6843 validate_protocol_v2_token("ls-refs attribute", attr)?;
6844 if attr.starts_with("peeled:") || attr.starts_with("symref-target:") {
6845 return Err(GitError::InvalidFormat(
6846 "ls-refs generic attributes must not duplicate known attributes".into(),
6847 ));
6848 }
6849 out.push(' ');
6850 out.push_str(attr);
6851 }
6852 Ok(())
6853}
6854
6855fn parse_fetch_section_header(payload: &[u8]) -> Result<String> {
6856 let name = parse_protocol_v2_line_text("fetch response section", payload)?;
6857 validate_capability_name(name)?;
6858 Ok(name.to_string())
6859}
6860
6861fn flush_terminates_protocol_v2_response(frames: &[PktLineFrame], idx: usize) -> bool {
6862 idx + 1 == frames.len()
6863 || (idx + 2 == frames.len() && matches!(frames[idx + 1], PktLineFrame::ResponseEnd))
6864}
6865
6866fn parse_fetch_section(
6867 format: ObjectFormat,
6868 name: String,
6869 lines: Vec<Vec<u8>>,
6870) -> Result<ProtocolV2FetchResponseSection> {
6871 match name.as_str() {
6872 "acknowledgments" => lines
6873 .iter()
6874 .map(|line| parse_fetch_acknowledgment(format, line))
6875 .collect::<Result<Vec<_>>>()
6876 .map(ProtocolV2FetchResponseSection::Acknowledgments),
6877 "shallow-info" => lines
6878 .iter()
6879 .map(|line| parse_fetch_shallow_info(format, line))
6880 .collect::<Result<Vec<_>>>()
6881 .map(ProtocolV2FetchResponseSection::ShallowInfo),
6882 "wanted-refs" => lines
6883 .iter()
6884 .map(|line| parse_fetch_wanted_ref(format, line))
6885 .collect::<Result<Vec<_>>>()
6886 .map(ProtocolV2FetchResponseSection::WantedRefs),
6887 "packfile-uris" => lines
6888 .iter()
6889 .map(|line| parse_fetch_packfile_uri(format, line))
6890 .collect::<Result<Vec<_>>>()
6891 .map(ProtocolV2FetchResponseSection::PackfileUris),
6892 "packfile" => Ok(ProtocolV2FetchResponseSection::Packfile(lines)),
6893 _ => Ok(ProtocolV2FetchResponseSection::Unknown { name, lines }),
6894 }
6895}
6896
6897fn parse_fetch_acknowledgment(
6898 format: ObjectFormat,
6899 line: &[u8],
6900) -> Result<ProtocolV2FetchAcknowledgment> {
6901 let text = parse_protocol_v2_line_text("fetch acknowledgment", line)?;
6902 match text {
6903 "NAK" => Ok(ProtocolV2FetchAcknowledgment::Nak),
6904 "ready" => Ok(ProtocolV2FetchAcknowledgment::Ready),
6905 value if value.starts_with("ACK ") => Ok(ProtocolV2FetchAcknowledgment::Ack(
6906 parse_oid_argument(format, "fetch ACK", value, "ACK ")?,
6907 )),
6908 other => Err(GitError::InvalidFormat(format!(
6909 "unsupported fetch acknowledgment {other}"
6910 ))),
6911 }
6912}
6913
6914fn parse_fetch_shallow_info(
6915 format: ObjectFormat,
6916 line: &[u8],
6917) -> Result<ProtocolV2FetchShallowInfo> {
6918 let text = parse_protocol_v2_line_text("fetch shallow-info", line)?;
6919 if text.starts_with("shallow ") {
6920 return Ok(ProtocolV2FetchShallowInfo::Shallow(parse_oid_argument(
6921 format,
6922 "fetch shallow",
6923 text,
6924 "shallow ",
6925 )?));
6926 }
6927 if text.starts_with("unshallow ") {
6928 return Ok(ProtocolV2FetchShallowInfo::Unshallow(parse_oid_argument(
6929 format,
6930 "fetch unshallow",
6931 text,
6932 "unshallow ",
6933 )?));
6934 }
6935 Err(GitError::InvalidFormat(format!(
6936 "unsupported fetch shallow-info {text}"
6937 )))
6938}
6939
6940fn parse_fetch_wanted_ref(format: ObjectFormat, line: &[u8]) -> Result<ProtocolV2FetchWantedRef> {
6941 let text = parse_protocol_v2_line_text("fetch wanted-ref", line)?;
6942 let (oid, name) = text
6943 .split_once(' ')
6944 .ok_or_else(|| GitError::InvalidFormat("fetch wanted-ref is missing name".into()))?;
6945 validate_protocol_v2_token("fetch wanted-ref name", name)?;
6946 Ok(ProtocolV2FetchWantedRef {
6947 oid: ObjectId::from_hex(format, oid)?,
6948 name: name.to_string(),
6949 })
6950}
6951
6952fn parse_fetch_packfile_uri(
6953 format: ObjectFormat,
6954 line: &[u8],
6955) -> Result<ProtocolV2FetchPackfileUri> {
6956 let text = parse_protocol_v2_line_text("fetch packfile-uri", line)?;
6957 let (pack_hash, uri) = text
6958 .split_once(' ')
6959 .ok_or_else(|| GitError::InvalidFormat("fetch packfile-uri is missing uri".into()))?;
6960 validate_protocol_v2_token("fetch packfile-uri hash", pack_hash)?;
6961 validate_protocol_v2_token("fetch packfile-uri", uri)?;
6962 Ok(ProtocolV2FetchPackfileUri {
6963 pack_hash: ObjectId::from_hex(format, pack_hash)?,
6964 uri: uri.to_string(),
6965 })
6966}
6967
6968fn protocol_v2_fetch_section_name(section: &ProtocolV2FetchResponseSection) -> &str {
6969 match section {
6970 ProtocolV2FetchResponseSection::Acknowledgments(_) => "acknowledgments",
6971 ProtocolV2FetchResponseSection::ShallowInfo(_) => "shallow-info",
6972 ProtocolV2FetchResponseSection::WantedRefs(_) => "wanted-refs",
6973 ProtocolV2FetchResponseSection::PackfileUris(_) => "packfile-uris",
6974 ProtocolV2FetchResponseSection::Packfile(_) => "packfile",
6975 ProtocolV2FetchResponseSection::Unknown { name, .. } => name,
6976 }
6977}
6978
6979fn format_protocol_v2_fetch_section_lines(
6980 section: &ProtocolV2FetchResponseSection,
6981) -> Result<Vec<Vec<u8>>> {
6982 match section {
6983 ProtocolV2FetchResponseSection::Acknowledgments(acks) => acks
6984 .iter()
6985 .map(|ack| match ack {
6986 ProtocolV2FetchAcknowledgment::Nak => Ok(line_from_str("NAK")),
6987 ProtocolV2FetchAcknowledgment::Ack(oid) => Ok(line_from_str(&format!("ACK {oid}"))),
6988 ProtocolV2FetchAcknowledgment::Ready => Ok(line_from_str("ready")),
6989 })
6990 .collect(),
6991 ProtocolV2FetchResponseSection::ShallowInfo(entries) => entries
6992 .iter()
6993 .map(|entry| match entry {
6994 ProtocolV2FetchShallowInfo::Shallow(oid) => {
6995 Ok(line_from_str(&format!("shallow {oid}")))
6996 }
6997 ProtocolV2FetchShallowInfo::Unshallow(oid) => {
6998 Ok(line_from_str(&format!("unshallow {oid}")))
6999 }
7000 })
7001 .collect(),
7002 ProtocolV2FetchResponseSection::WantedRefs(refs) => refs
7003 .iter()
7004 .map(|wanted| {
7005 validate_protocol_v2_token("fetch wanted-ref name", &wanted.name)?;
7006 Ok(line_from_str(&format!("{} {}", wanted.oid, wanted.name)))
7007 })
7008 .collect(),
7009 ProtocolV2FetchResponseSection::PackfileUris(uris) => uris
7010 .iter()
7011 .map(|packfile_uri| {
7012 validate_protocol_v2_token("fetch packfile-uri", &packfile_uri.uri)?;
7013 Ok(line_from_str(&format!(
7014 "{} {}",
7015 packfile_uri.pack_hash, packfile_uri.uri
7016 )))
7017 })
7018 .collect(),
7019 ProtocolV2FetchResponseSection::Packfile(lines) => Ok(lines.clone()),
7020 ProtocolV2FetchResponseSection::Unknown { name, lines } => {
7021 validate_capability_name(name)?;
7022 for line in lines {
7023 validate_protocol_v2_line("fetch unknown section line", line)?;
7024 }
7025 Ok(lines.clone())
7026 }
7027 }
7028}
7029
7030fn parse_protocol_v2_object_info_record(
7031 format: ObjectFormat,
7032 line: &[u8],
7033) -> Result<ProtocolV2ObjectInfoRecord> {
7034 let text = parse_protocol_v2_line_text("object-info record", line)?;
7035 let mut fields = text.split(' ');
7036 let oid = fields
7037 .next()
7038 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing oid".into()))?;
7039 let size = fields
7040 .next()
7041 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing size".into()))?;
7042 if fields.next().is_some() {
7043 return Err(GitError::InvalidFormat(
7044 "object-info record has too many fields".into(),
7045 ));
7046 }
7047 validate_protocol_v2_token("object-info oid", oid)?;
7048 validate_protocol_v2_token("object-info size", size)?;
7049 let size = size
7050 .parse::<u64>()
7051 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7052 Ok(ProtocolV2ObjectInfoRecord {
7053 oid: ObjectId::from_hex(format, oid)?,
7054 size,
7055 })
7056}
7057
7058fn parse_dumb_http_info_ref_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpRefRecord> {
7059 validate_dumb_http_info_ref_line(line)?;
7060 let line = trim_trailing_lf(line);
7061 let tab = line
7062 .iter()
7063 .position(|byte| *byte == b'\t')
7064 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP ref record is missing name".into()))?;
7065 let (oid, name) = (&line[..tab], &line[tab + 1..]);
7066 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7067 validate_protocol_v2_token("dumb HTTP ref oid", oid)?;
7068 let name = std::str::from_utf8(name).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7069 let (name, peeled) = name
7070 .strip_suffix("^{}")
7071 .map_or((name, false), |name| (name, true));
7072 validate_dumb_http_ref_name(name)?;
7073 Ok(DumbHttpRefRecord {
7074 oid: ObjectId::from_hex(format, oid)?,
7075 name: name.to_string(),
7076 peeled,
7077 })
7078}
7079
7080fn parse_dumb_http_alternate(line: &[u8]) -> Result<String> {
7081 validate_dumb_http_alternate_line(line)?;
7082 let line = trim_trailing_lf(line);
7083 let alternate =
7084 std::str::from_utf8(line).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7085 validate_dumb_http_alternate(alternate)?;
7086 Ok(alternate.to_string())
7087}
7088
7089fn parse_dumb_http_pack_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpPackRecord> {
7090 validate_dumb_http_info_ref_line(line)?;
7091 let line = parse_protocol_v2_line_text("dumb HTTP pack record", line)?;
7092 let pack_name = line
7093 .strip_prefix("P ")
7094 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP pack record must start with P".into()))?;
7095 let hash = pack_name
7096 .strip_prefix("pack-")
7097 .and_then(|value| value.strip_suffix(".pack"))
7098 .ok_or_else(|| GitError::InvalidFormat("invalid dumb HTTP pack name".into()))?;
7099 validate_protocol_v2_token("dumb HTTP pack hash", hash)?;
7100 Ok(DumbHttpPackRecord {
7101 hash: ObjectId::from_hex(format, hash)?,
7102 })
7103}
7104
7105fn encode_protocol_v2_capability(capability: &Capability) -> Result<Vec<u8>> {
7106 validate_capability_name(&capability.name)?;
7107 let mut out = capability.name.as_bytes().to_vec();
7108 if let Some(value) = &capability.value {
7109 validate_protocol_v2_capability_value(value)?;
7110 out.push(b'=');
7111 out.extend_from_slice(value.as_bytes());
7112 }
7113 Ok(out)
7114}
7115
7116fn validate_capability_field(label: &str, value: &str) -> Result<()> {
7117 if value.is_empty() {
7118 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7119 }
7120 if value
7121 .bytes()
7122 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t' | 0))
7123 {
7124 return Err(GitError::InvalidFormat(format!(
7125 "{label} contains a delimiter byte"
7126 )));
7127 }
7128 Ok(())
7129}
7130
7131fn validate_capability_name(value: &str) -> Result<()> {
7132 validate_capability_field("capability name", value)?;
7133 if value.bytes().any(|byte| byte == b'=') {
7134 return Err(GitError::InvalidFormat(
7135 "capability name contains a delimiter byte".into(),
7136 ));
7137 }
7138 Ok(())
7139}
7140
7141fn validate_protocol_v2_capability_value(value: &str) -> Result<()> {
7142 if value.is_empty() {
7143 return Err(GitError::InvalidFormat(
7144 "protocol v2 capability value is empty".into(),
7145 ));
7146 }
7147 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7148 return Err(GitError::InvalidFormat(
7149 "protocol v2 capability value contains a delimiter byte".into(),
7150 ));
7151 }
7152 Ok(())
7153}
7154
7155fn validate_protocol_v2_argument(value: &[u8]) -> Result<()> {
7156 if value.is_empty() {
7157 return Err(GitError::InvalidFormat(
7158 "protocol v2 command argument is empty".into(),
7159 ));
7160 }
7161 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
7162 return Err(GitError::InvalidFormat(
7163 "protocol v2 command argument contains a delimiter byte".into(),
7164 ));
7165 }
7166 Ok(())
7167}
7168
7169fn validate_upload_archive_argument(value: &str) -> Result<()> {
7170 if value.is_empty() {
7171 return Err(GitError::InvalidFormat(
7172 "upload-archive argument is empty".into(),
7173 ));
7174 }
7175 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7176 return Err(GitError::InvalidFormat(
7177 "upload-archive argument contains a delimiter byte".into(),
7178 ));
7179 }
7180 Ok(())
7181}
7182
7183fn validate_upload_archive_status_message(value: &str) -> Result<()> {
7184 if value.is_empty() {
7185 return Err(GitError::InvalidFormat(
7186 "upload-archive status message is empty".into(),
7187 ));
7188 }
7189 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7190 return Err(GitError::InvalidFormat(
7191 "upload-archive status message contains a delimiter byte".into(),
7192 ));
7193 }
7194 Ok(())
7195}
7196
7197fn non_empty(value: &str) -> Option<&str> {
7198 (!value.is_empty()).then_some(value)
7199}
7200
7201fn validate_refspec_value(value: &str) -> Result<()> {
7202 if value.is_empty() {
7203 return Err(GitError::InvalidFormat("refspec is empty".into()));
7204 }
7205 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7206 return Err(GitError::InvalidFormat(
7207 "refspec contains a delimiter byte".into(),
7208 ));
7209 }
7210 Ok(())
7211}
7212
7213fn validate_refspec_endpoint(label: &str, value: &str) -> Result<()> {
7214 if value.is_empty() {
7215 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7216 }
7217 if value
7218 .bytes()
7219 .any(|byte| matches!(byte, b':' | b' ' | b'\t' | b'\n' | b'\r' | 0))
7220 {
7221 return Err(GitError::InvalidFormat(format!(
7222 "{label} contains a delimiter byte"
7223 )));
7224 }
7225 Ok(())
7226}
7227
7228fn count_refspec_wildcards(value: &str) -> usize {
7229 value.bytes().filter(|byte| *byte == b'*').count()
7230}
7231
7232fn validate_refspec_shape(refspec: &RefSpec) -> Result<()> {
7233 if refspec.force && refspec.negative {
7234 return Err(GitError::InvalidFormat(
7235 "negative refspec must not be forced".into(),
7236 ));
7237 }
7238 if refspec.negative && refspec.dst.is_some() {
7239 return Err(GitError::InvalidFormat(
7240 "negative refspec must not have a destination".into(),
7241 ));
7242 }
7243 if refspec.negative && refspec.src.is_none() {
7244 return Err(GitError::InvalidFormat(
7245 "negative refspec is missing a source".into(),
7246 ));
7247 }
7248 if refspec.src.is_none() && refspec.dst.is_none() && refspec.negative {
7249 return Err(GitError::InvalidFormat(
7250 "refspec must include a source or destination".into(),
7251 ));
7252 }
7253 if let Some(src) = &refspec.src {
7254 validate_refspec_endpoint("refspec source", src)?;
7255 }
7256 if let Some(dst) = &refspec.dst {
7257 validate_refspec_endpoint("refspec destination", dst)?;
7258 }
7259 let src_pattern_count = refspec
7260 .src
7261 .as_deref()
7262 .map(count_refspec_wildcards)
7263 .unwrap_or(0);
7264 let dst_pattern_count = refspec
7265 .dst
7266 .as_deref()
7267 .map(count_refspec_wildcards)
7268 .unwrap_or(0);
7269 if src_pattern_count > 1 || dst_pattern_count > 1 {
7270 return Err(GitError::InvalidFormat(
7271 "refspec endpoint has too many wildcards".into(),
7272 ));
7273 }
7274 if refspec.dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
7275 return Err(GitError::InvalidFormat(
7276 "refspec wildcard must appear in both source and destination".into(),
7277 ));
7278 }
7279 if refspec.pattern != (src_pattern_count == 1 || dst_pattern_count == 1) {
7280 return Err(GitError::InvalidFormat(
7281 "refspec pattern flag does not match endpoints".into(),
7282 ));
7283 }
7284 Ok(())
7285}
7286
7287fn parse_fetch_head_record(format: ObjectFormat, line: &[u8]) -> Result<FetchHeadRecord> {
7288 validate_fetch_head_line(line)?;
7289 let line = trim_trailing_lf(line);
7290 let mut fields = line.splitn(3, |byte| *byte == b'\t');
7291 let oid = fields
7292 .next()
7293 .ok_or_else(|| GitError::InvalidFormat("FETCH_HEAD record is missing oid".into()))?;
7294 let merge_marker = fields.next().ok_or_else(|| {
7295 GitError::InvalidFormat("FETCH_HEAD record is missing merge marker".into())
7296 })?;
7297 let description = fields.next().ok_or_else(|| {
7298 GitError::InvalidFormat("FETCH_HEAD record is missing description".into())
7299 })?;
7300 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7301 validate_protocol_v2_token("FETCH_HEAD oid", oid)?;
7302 let not_for_merge = match merge_marker {
7303 b"" => false,
7304 b"not-for-merge" => true,
7305 _ => {
7306 return Err(GitError::InvalidFormat(
7307 "FETCH_HEAD record has invalid merge marker".into(),
7308 ));
7309 }
7310 };
7311 let description =
7312 std::str::from_utf8(description).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7313 validate_fetch_head_description_field(description)?;
7314 Ok(FetchHeadRecord {
7315 oid: ObjectId::from_hex(format, oid)?,
7316 not_for_merge,
7317 description: description.to_string(),
7318 })
7319}
7320
7321fn validate_fetch_head_line(value: &[u8]) -> Result<()> {
7322 if value.is_empty() {
7323 return Err(GitError::InvalidFormat("FETCH_HEAD record is empty".into()));
7324 }
7325 if !value.ends_with(b"\n") {
7326 return Err(GitError::InvalidFormat(
7327 "FETCH_HEAD record missing LF".into(),
7328 ));
7329 }
7330 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7331 return Err(GitError::InvalidFormat(
7332 "FETCH_HEAD record contains a delimiter byte".into(),
7333 ));
7334 }
7335 Ok(())
7336}
7337
7338fn validate_fetch_head_description_field(value: &str) -> Result<()> {
7339 if value.is_empty() {
7340 return Err(GitError::InvalidFormat(
7341 "FETCH_HEAD description is empty".into(),
7342 ));
7343 }
7344 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7345 return Err(GitError::InvalidFormat(
7346 "FETCH_HEAD description contains a delimiter byte".into(),
7347 ));
7348 }
7349 Ok(())
7350}
7351
7352fn refspec_is_excluded(negative: &[&RefSpec], source: &str) -> Result<bool> {
7353 for refspec in negative {
7354 if refspec_matches_source(refspec, source)? {
7355 return Ok(true);
7356 }
7357 }
7358 Ok(false)
7359}
7360
7361fn zero_object_id(format: ObjectFormat) -> Result<ObjectId> {
7362 Ok(ObjectId::null(format))
7363}
7364
7365fn local_ref<'a>(refs: &'a [PushSourceRef], name: &str) -> Option<&'a PushSourceRef> {
7366 refs.iter().find(|reference| reference.name == name)
7367}
7368
7369fn remote_ref<'a>(refs: &'a [RefAdvertisement], name: &str) -> Option<&'a RefAdvertisement> {
7370 refs.iter().find(|reference| reference.name == name)
7371}
7372
7373fn validate_push_source_ref(format: ObjectFormat, reference: &PushSourceRef) -> Result<()> {
7374 if reference.oid.format() != format {
7375 return Err(GitError::InvalidObjectId(
7376 "push source ref object format does not match repository".into(),
7377 ));
7378 }
7379 validate_refspec_endpoint("push source ref name", &reference.name)
7380}
7381
7382fn require_receive_pack_feature(advertised: bool, name: &str) -> Result<()> {
7383 if advertised {
7384 Ok(())
7385 } else {
7386 Err(GitError::InvalidFormat(format!(
7387 "receive-pack feature {name} was not advertised"
7388 )))
7389 }
7390}
7391
7392fn validate_smart_http_service(service: GitService) -> Result<()> {
7393 match service {
7394 GitService::UploadPack | GitService::ReceivePack => Ok(()),
7395 GitService::UploadArchive => Err(GitError::InvalidFormat(
7396 "smart HTTP only supports upload-pack and receive-pack services".into(),
7397 )),
7398 }
7399}
7400
7401fn normalize_http_repository_path(path: &str) -> Result<String> {
7402 if path.is_empty() {
7403 return Err(GitError::InvalidFormat(
7404 "smart HTTP repository path is empty".into(),
7405 ));
7406 }
7407 if !path.starts_with('/') {
7408 return Err(GitError::InvalidFormat(
7409 "smart HTTP repository path must start with /".into(),
7410 ));
7411 }
7412 if path
7413 .bytes()
7414 .any(|byte| matches!(byte, b'\n' | b'\r' | 0 | b'?' | b'#'))
7415 {
7416 return Err(GitError::InvalidFormat(
7417 "smart HTTP repository path contains a delimiter byte".into(),
7418 ));
7419 }
7420 let normalized = path.trim_end_matches('/');
7421 Ok(if normalized.is_empty() {
7422 "/".into()
7423 } else {
7424 normalized.to_string()
7425 })
7426}
7427
7428fn dumb_http_pack_resource_path(
7429 repository_path: &str,
7430 hash: &ObjectId,
7431 suffix: &str,
7432) -> Result<String> {
7433 let repository_path = normalize_http_repository_path(repository_path)?;
7434 Ok(format!(
7435 "{repository_path}/objects/pack/pack-{hash}.{suffix}"
7436 ))
7437}
7438
7439fn parse_smart_http_content_type(value: &str, suffix: &str) -> Result<GitService> {
7440 let value = value.trim();
7441 if value.is_empty() {
7442 return Err(GitError::InvalidFormat(
7443 "smart HTTP content type is empty".into(),
7444 ));
7445 }
7446 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7447 return Err(GitError::InvalidFormat(
7448 "smart HTTP content type contains a delimiter byte".into(),
7449 ));
7450 }
7451 let value = value.to_ascii_lowercase();
7452 let service = value
7453 .strip_prefix("application/x-")
7454 .and_then(|value| value.strip_suffix(suffix))
7455 .ok_or_else(|| GitError::InvalidFormat("invalid smart HTTP content type".into()))?;
7456 let service = parse_git_service(service)?;
7457 validate_smart_http_service(service)?;
7458 Ok(service)
7459}
7460
7461fn validate_dumb_http_info_ref_line(value: &[u8]) -> Result<()> {
7462 if value.is_empty() {
7463 return Err(GitError::InvalidFormat(
7464 "dumb HTTP ref record is empty".into(),
7465 ));
7466 }
7467 if !value.ends_with(b"\n") {
7468 return Err(GitError::InvalidFormat(
7469 "dumb HTTP ref record missing LF".into(),
7470 ));
7471 }
7472 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7473 return Err(GitError::InvalidFormat(
7474 "dumb HTTP ref record contains a delimiter byte".into(),
7475 ));
7476 }
7477 Ok(())
7478}
7479
7480fn validate_dumb_http_ref_name(value: &str) -> Result<()> {
7481 validate_protocol_v2_token("dumb HTTP ref name", value)?;
7482 if value.ends_with("^{}") {
7483 return Err(GitError::InvalidFormat(
7484 "dumb HTTP ref name must not include peeled suffix".into(),
7485 ));
7486 }
7487 Ok(())
7488}
7489
7490fn validate_dumb_http_alternate_line(value: &[u8]) -> Result<()> {
7491 if value.is_empty() {
7492 return Err(GitError::InvalidFormat(
7493 "dumb HTTP alternate is empty".into(),
7494 ));
7495 }
7496 if !value.ends_with(b"\n") {
7497 return Err(GitError::InvalidFormat(
7498 "dumb HTTP alternate missing LF".into(),
7499 ));
7500 }
7501 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7502 return Err(GitError::InvalidFormat(
7503 "dumb HTTP alternate contains a delimiter byte".into(),
7504 ));
7505 }
7506 Ok(())
7507}
7508
7509fn validate_dumb_http_alternate(value: &str) -> Result<()> {
7510 if value.is_empty() {
7511 return Err(GitError::InvalidFormat(
7512 "dumb HTTP alternate is empty".into(),
7513 ));
7514 }
7515 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7516 return Err(GitError::InvalidFormat(
7517 "dumb HTTP alternate contains a delimiter byte".into(),
7518 ));
7519 }
7520 Ok(())
7521}
7522
7523fn validate_protocol_v2_token(label: &str, value: &str) -> Result<()> {
7524 if value.is_empty() {
7525 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7526 }
7527 if value
7528 .bytes()
7529 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | 0))
7530 {
7531 return Err(GitError::InvalidFormat(format!(
7532 "{label} contains a delimiter byte"
7533 )));
7534 }
7535 Ok(())
7536}
7537
7538fn validate_protocol_v2_line(label: &str, value: &[u8]) -> Result<()> {
7539 if value.is_empty() {
7540 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7541 }
7542 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7543 return Err(GitError::InvalidFormat(format!(
7544 "{label} contains a delimiter byte"
7545 )));
7546 }
7547 Ok(())
7548}
7549
7550fn parse_protocol_v2_line_text<'a>(label: &str, value: &'a [u8]) -> Result<&'a str> {
7551 validate_protocol_v2_line(label, value)?;
7552 let value = trim_trailing_lf(value);
7553 if value.is_empty() {
7554 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7555 }
7556 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
7557 return Err(GitError::InvalidFormat(format!(
7558 "{label} contains a delimiter byte"
7559 )));
7560 }
7561 std::str::from_utf8(value).map_err(|err| GitError::InvalidFormat(err.to_string()))
7562}
7563
7564fn parse_oid_argument(
7565 format: ObjectFormat,
7566 label: &str,
7567 value: &str,
7568 prefix: &str,
7569) -> Result<ObjectId> {
7570 let oid = value
7571 .strip_prefix(prefix)
7572 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7573 validate_protocol_v2_token(label, oid)?;
7574 ObjectId::from_hex(format, oid)
7575}
7576
7577fn parse_u32_argument(label: &str, value: &str, prefix: &str) -> Result<u32> {
7578 let number = value
7579 .strip_prefix(prefix)
7580 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7581 validate_protocol_v2_token(label, number)?;
7582 let parsed = number
7583 .parse::<u32>()
7584 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7585 if parsed == 0 {
7586 return Err(GitError::InvalidFormat(format!("{label} must be positive")));
7587 }
7588 Ok(parsed)
7589}
7590
7591fn parse_u64_argument(label: &str, value: &str, prefix: &str) -> Result<u64> {
7592 let number = value
7593 .strip_prefix(prefix)
7594 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7595 validate_protocol_v2_token(label, number)?;
7596 number
7597 .parse::<u64>()
7598 .map_err(|err| GitError::InvalidFormat(err.to_string()))
7599}
7600
7601fn line(mut payload: Vec<u8>) -> Vec<u8> {
7602 payload.push(b'\n');
7603 payload
7604}
7605
7606fn line_from_str(payload: &str) -> Vec<u8> {
7607 line(payload.as_bytes().to_vec())
7608}
7609
7610fn trim_trailing_lf(input: &[u8]) -> &[u8] {
7611 input.strip_suffix(b"\n").unwrap_or(input)
7612}
7613
7614#[cfg(test)]
7615mod tests {
7616 use super::*;
7617
7618 #[test]
7619 fn pkt_line_frame_encodes_data_and_control_frames() {
7620 assert_eq!(PktLine(b"hello\n".to_vec()).encode(), b"000ahello\n");
7621 assert_eq!(
7622 PktLineFrame::data(b"hello\n".to_vec())
7623 .expect("test operation should succeed")
7624 .encode(),
7625 b"000ahello\n"
7626 );
7627 assert_eq!(PktLineFrame::Flush.encode(), b"0000");
7628 assert_eq!(PktLineFrame::Delimiter.encode(), b"0001");
7629 assert_eq!(PktLineFrame::ResponseEnd.encode(), b"0002");
7630 assert_eq!(
7631 PktLineFrame::data(b"hello\n".to_vec())
7632 .expect("test operation should succeed")
7633 .try_encode()
7634 .expect("test operation should succeed"),
7635 b"000ahello\n"
7636 );
7637 }
7638
7639 #[test]
7640 fn pkt_line_frame_parses_data_and_control_frames() {
7641 assert_eq!(
7642 PktLineFrame::parse(b"000ahello\n").expect("test operation should succeed"),
7643 (PktLineFrame::Data(b"hello\n".to_vec()), 10)
7644 );
7645 assert_eq!(
7646 PktLineFrame::parse(b"0000").expect("test operation should succeed"),
7647 (PktLineFrame::Flush, 4)
7648 );
7649 assert_eq!(
7650 PktLineFrame::parse(b"0001").expect("test operation should succeed"),
7651 (PktLineFrame::Delimiter, 4)
7652 );
7653 assert_eq!(
7654 PktLineFrame::parse(b"0002").expect("test operation should succeed"),
7655 (PktLineFrame::ResponseEnd, 4)
7656 );
7657 }
7658
7659 #[test]
7660 fn pkt_line_stream_parses_multiple_frames() {
7661 let frames = parse_pkt_line_stream(b"000eversion 2\n00010009done\n0000")
7662 .expect("test operation should succeed");
7663 assert_eq!(
7664 frames,
7665 vec![
7666 PktLineFrame::Data(b"version 2\n".to_vec()),
7667 PktLineFrame::Delimiter,
7668 PktLineFrame::Data(b"done\n".to_vec()),
7669 PktLineFrame::Flush,
7670 ]
7671 );
7672 }
7673
7674 #[test]
7675 fn pkt_line_stream_reads_and_writes_incremental_io() {
7676 let frames = vec![
7677 PktLineFrame::Data(b"version 2\n".to_vec()),
7678 PktLineFrame::Delimiter,
7679 PktLineFrame::Data(b"done\n".to_vec()),
7680 PktLineFrame::Flush,
7681 ];
7682 let mut encoded = Vec::new();
7683 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
7684 assert_eq!(encoded, b"000eversion 2\n00010009done\n0000");
7685 assert_eq!(
7686 read_pkt_line_frames(&mut encoded.as_slice()).expect("test operation should succeed"),
7687 frames
7688 );
7689
7690 let mut empty: &[u8] = b"";
7691 assert_eq!(
7692 read_pkt_line_frame(&mut empty).expect("test operation should succeed"),
7693 None
7694 );
7695 }
7696
7697 #[test]
7698 fn pkt_line_stream_reads_until_control_packets() {
7699 let input = b"000eversion 2\n0000trailing";
7700 let frames = read_pkt_line_frames_until_flush(&mut &input[..])
7701 .expect("test operation should succeed");
7702 assert_eq!(
7703 frames,
7704 vec![
7705 PktLineFrame::Data(b"version 2\n".to_vec()),
7706 PktLineFrame::Flush,
7707 ]
7708 );
7709
7710 let input = b"0009done\n0002next";
7711 let frames = read_pkt_line_frames_until_response_end(&mut &input[..])
7712 .expect("test operation should succeed");
7713 assert_eq!(
7714 frames,
7715 vec![
7716 PktLineFrame::Data(b"done\n".to_vec()),
7717 PktLineFrame::ResponseEnd,
7718 ]
7719 );
7720 assert!(read_pkt_line_frames_until_flush(&mut &b"0009done\n"[..]).is_err());
7721 }
7722
7723 #[test]
7724 fn pkt_line_rejects_invalid_lengths() {
7725 assert!(PktLineFrame::parse(b"000").is_err());
7726 assert!(PktLineFrame::parse(b"0003").is_err());
7727 assert!(PktLineFrame::parse(b"000ahello").is_err());
7728 assert!(PktLineFrame::parse(b"zzzz").is_err());
7729 assert!(read_pkt_line_frame(&mut &b"000"[..]).is_err());
7730 assert!(read_pkt_line_frame(&mut &b"0003"[..]).is_err());
7731 }
7732
7733 #[test]
7734 fn pkt_line_rejects_oversized_data() {
7735 let payload = vec![b'x'; PKT_LINE_MAX_PAYLOAD_LEN + 1];
7736 assert!(PktLineFrame::data(payload.clone()).is_err());
7737 assert!(PktLine(payload.clone()).try_encode().is_err());
7738 assert!(PktLineFrame::Data(payload.clone()).try_encode().is_err());
7739 assert!(write_pkt_line_frame(&mut Vec::new(), &PktLineFrame::Data(payload)).is_err());
7740 assert!(PktLineFrame::parse(b"fff1").is_err());
7741 }
7742
7743 #[test]
7744 fn protocol_error_lines_parse_encode_and_stream() {
7745 let error = parse_error_line(b"ERR remote rejected request\n")
7746 .expect("test operation should succeed");
7747 assert_eq!(
7748 error,
7749 ProtocolErrorLine {
7750 message: "remote rejected request".into(),
7751 }
7752 );
7753 assert_eq!(
7754 encode_error_line(&error).expect("test operation should succeed"),
7755 b"ERR remote rejected request\n"
7756 );
7757 assert_eq!(
7758 parse_error_frame(&PktLineFrame::Data(
7759 b"ERR remote rejected request\n".to_vec()
7760 ))
7761 .expect("test operation should succeed"),
7762 Some(error.clone())
7763 );
7764 assert_eq!(
7765 parse_error_frame(&PktLineFrame::Data(b"NAK\n".to_vec()))
7766 .expect("test operation should succeed"),
7767 None
7768 );
7769
7770 let mut encoded = Vec::new();
7771 write_error_line(&mut encoded, &error).expect("test operation should succeed");
7772 encoded.extend_from_slice(b"tail");
7773 let mut input = encoded.as_slice();
7774 assert_eq!(
7775 read_error_line(&mut input).expect("test operation should succeed"),
7776 error
7777 );
7778 assert_eq!(input, b"tail");
7779 }
7780
7781 #[test]
7782 fn protocol_error_lines_reject_malformed_messages() {
7783 assert!(parse_error_line(b"ERR\n").is_err());
7784 assert!(parse_error_line(b"ERR \n").is_err());
7785 assert!(parse_error_line(b"ERR bad\0message\n").is_err());
7786 assert!(parse_error_line(b"NAK\n").is_err());
7787 assert!(
7788 encode_error_line(&ProtocolErrorLine {
7789 message: "bad\nmessage".into(),
7790 })
7791 .is_err()
7792 );
7793 assert!(read_error_line(&mut &b"0000"[..]).is_err());
7794 }
7795
7796 #[test]
7797 fn refspec_parser_handles_fetch_push_and_negative_forms() {
7798 assert_eq!(
7799 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7800 .expect("test operation should succeed"),
7801 RefSpec {
7802 force: true,
7803 negative: false,
7804 src: Some("refs/heads/*".into()),
7805 dst: Some("refs/remotes/origin/*".into()),
7806 pattern: true,
7807 }
7808 );
7809 assert_eq!(
7810 parse_refspec("refs/heads/main").expect("test operation should succeed"),
7811 RefSpec {
7812 force: false,
7813 negative: false,
7814 src: Some("refs/heads/main".into()),
7815 dst: None,
7816 pattern: false,
7817 }
7818 );
7819 assert_eq!(
7820 parse_refspec(":refs/heads/topic").expect("test operation should succeed"),
7821 RefSpec {
7822 force: false,
7823 negative: false,
7824 src: None,
7825 dst: Some("refs/heads/topic".into()),
7826 pattern: false,
7827 }
7828 );
7829 assert_eq!(
7830 parse_refspec(":").expect("test operation should succeed"),
7831 RefSpec {
7832 force: false,
7833 negative: false,
7834 src: None,
7835 dst: None,
7836 pattern: false,
7837 }
7838 );
7839 assert_eq!(
7840 parse_refspec("^refs/tags/private/*").expect("test operation should succeed"),
7841 RefSpec {
7842 force: false,
7843 negative: true,
7844 src: Some("refs/tags/private/*".into()),
7845 dst: None,
7846 pattern: true,
7847 }
7848 );
7849 }
7850
7851 #[test]
7852 fn refspec_encode_and_map_sources() {
7853 let pattern = parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7854 .expect("test operation should succeed");
7855 assert_eq!(
7856 encode_refspec(&pattern).expect("test operation should succeed"),
7857 "+refs/heads/*:refs/remotes/origin/*"
7858 );
7859 assert!(
7860 refspec_matches_source(&pattern, "refs/heads/main")
7861 .expect("test operation should succeed")
7862 );
7863 assert_eq!(
7864 refspec_map_source(&pattern, "refs/heads/main").expect("test operation should succeed"),
7865 Some("refs/remotes/origin/main".into())
7866 );
7867 assert_eq!(
7868 refspec_map_source(&pattern, "refs/tags/v1").expect("test operation should succeed"),
7869 None
7870 );
7871
7872 let direct = parse_refspec("HEAD:refs/heads/main").expect("test operation should succeed");
7873 assert_eq!(
7874 encode_refspec(&direct).expect("test operation should succeed"),
7875 "HEAD:refs/heads/main"
7876 );
7877 assert_eq!(
7878 refspec_map_source(&direct, "HEAD").expect("test operation should succeed"),
7879 Some("refs/heads/main".into())
7880 );
7881
7882 let delete = parse_refspec(":refs/heads/old").expect("test operation should succeed");
7883 assert_eq!(
7884 encode_refspec(&delete).expect("test operation should succeed"),
7885 ":refs/heads/old"
7886 );
7887 assert_eq!(
7888 refspec_map_source(&delete, "HEAD").expect("test operation should succeed"),
7889 None
7890 );
7891
7892 let matching = parse_refspec(":").expect("test operation should succeed");
7893 assert_eq!(
7894 encode_refspec(&matching).expect("test operation should succeed"),
7895 ":"
7896 );
7897 }
7898
7899 #[test]
7900 fn refspec_parser_rejects_malformed_values() {
7901 assert!(parse_refspec("").is_err());
7902 assert!(parse_refspec("+^refs/heads/main").is_err());
7903 assert!(parse_refspec("^refs/heads/main:refs/remotes/origin/main").is_err());
7904 assert!(parse_refspec("refs/heads/*:refs/remotes/origin/main").is_err());
7905 assert!(parse_refspec("refs/heads/**:refs/remotes/origin/*").is_err());
7906 assert!(parse_refspec("refs/heads/main:refs/remotes/origin/main:extra").is_err());
7907 assert!(parse_refspec("refs/heads/main\n").is_err());
7908 assert!(
7909 encode_refspec(&RefSpec {
7910 force: false,
7911 negative: false,
7912 src: Some("refs/heads/*".into()),
7913 dst: Some("refs/remotes/origin/main".into()),
7914 pattern: true,
7915 })
7916 .is_err()
7917 );
7918 }
7919
7920 #[test]
7921 fn fetch_head_records_parse_encode_and_describe_refs() {
7922 let first = ObjectId::from_hex(
7923 ObjectFormat::Sha1,
7924 "1111111111111111111111111111111111111111",
7925 )
7926 .expect("test operation should succeed");
7927 let second = ObjectId::from_hex(
7928 ObjectFormat::Sha1,
7929 "2222222222222222222222222222222222222222",
7930 )
7931 .expect("test operation should succeed");
7932 let input = b"1111111111111111111111111111111111111111\t\tbranch 'main' of ../bundle.bdl\n2222222222222222222222222222222222222222\tnot-for-merge\ttag 'v1' of ../bundle.bdl\n";
7933 let records =
7934 parse_fetch_head(ObjectFormat::Sha1, input).expect("test operation should succeed");
7935 assert_eq!(
7936 records,
7937 vec![
7938 FetchHeadRecord {
7939 oid: first,
7940 not_for_merge: false,
7941 description: "branch 'main' of ../bundle.bdl".into(),
7942 },
7943 FetchHeadRecord {
7944 oid: second,
7945 not_for_merge: true,
7946 description: "tag 'v1' of ../bundle.bdl".into(),
7947 },
7948 ]
7949 );
7950 assert_eq!(
7951 encode_fetch_head(&records).expect("test operation should succeed"),
7952 input
7953 );
7954 assert_eq!(
7955 parse_fetch_head(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
7956 Vec::<FetchHeadRecord>::new()
7957 );
7958 assert_eq!(
7959 fetch_head_remote_description("refs/heads/main", "../bundle.bdl")
7960 .expect("test operation should succeed"),
7961 "branch 'main' of ../bundle.bdl"
7962 );
7963 assert_eq!(
7964 fetch_head_remote_description("refs/tags/v1", "../bundle.bdl")
7965 .expect("test operation should succeed"),
7966 "tag 'v1' of ../bundle.bdl"
7967 );
7968 assert_eq!(
7970 fetch_head_remote_description("HEAD", "../bundle.bdl")
7971 .expect("test operation should succeed"),
7972 "../bundle.bdl"
7973 );
7974 }
7975
7976 #[test]
7977 fn fetch_head_records_streams_round_trip() {
7978 let records = vec![FetchHeadRecord {
7979 oid: ObjectId::from_hex(
7980 ObjectFormat::Sha1,
7981 "1111111111111111111111111111111111111111",
7982 )
7983 .expect("test operation should succeed"),
7984 not_for_merge: false,
7985 description: "branch 'main' of ../bundle.bdl".into(),
7986 }];
7987 let mut encoded = Vec::new();
7988 write_fetch_head(&mut encoded, &records).expect("test operation should succeed");
7989 let mut input = encoded.as_slice();
7990 assert_eq!(
7991 read_fetch_head(ObjectFormat::Sha1, &mut input).expect("test operation should succeed"),
7992 records
7993 );
7994 assert!(input.is_empty());
7995 }
7996
7997 #[test]
7998 fn fetch_head_records_reject_malformed_lines() {
7999 assert!(
8000 parse_fetch_head(
8001 ObjectFormat::Sha1,
8002 b"1111111111111111111111111111111111111111\t\tbranch 'main'"
8003 )
8004 .is_err()
8005 );
8006 assert!(
8007 parse_fetch_head(
8008 ObjectFormat::Sha1,
8009 b"1111111111111111111111111111111111111111\tfor-merge\tbranch 'main'\n"
8010 )
8011 .is_err()
8012 );
8013 assert!(parse_fetch_head(ObjectFormat::Sha1, b"not-a-hash\t\tbranch 'main'\n").is_err());
8014 assert!(
8015 encode_fetch_head(&[FetchHeadRecord {
8016 oid: ObjectId::from_hex(
8017 ObjectFormat::Sha1,
8018 "1111111111111111111111111111111111111111"
8019 )
8020 .expect("test operation should succeed"),
8021 not_for_merge: false,
8022 description: "bad\ndescription".into(),
8023 }])
8024 .is_err()
8025 );
8026 }
8027
8028 #[test]
8029 fn fetch_planner_maps_direct_pattern_and_negative_refspecs() {
8030 let main = ObjectId::from_hex(
8031 ObjectFormat::Sha1,
8032 "1111111111111111111111111111111111111111",
8033 )
8034 .expect("test operation should succeed");
8035 let next = ObjectId::from_hex(
8036 ObjectFormat::Sha1,
8037 "2222222222222222222222222222222222222222",
8038 )
8039 .expect("test operation should succeed");
8040 let refs = vec![
8041 RefAdvertisement {
8042 oid: main.clone(),
8043 name: "refs/heads/main".into(),
8044 capabilities: Vec::new(),
8045 },
8046 RefAdvertisement {
8047 oid: next.clone(),
8048 name: "refs/heads/tmp".into(),
8049 capabilities: Vec::new(),
8050 },
8051 ];
8052 let refspecs = vec![
8053 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
8054 .expect("test operation should succeed"),
8055 parse_refspec("^refs/heads/tmp").expect("test operation should succeed"),
8056 ];
8057 assert_eq!(
8058 plan_fetch_ref_updates(&refs, &refspecs, false).expect("test operation should succeed"),
8059 vec![FetchRefUpdate {
8060 src: "refs/heads/main".into(),
8061 dst: Some("refs/remotes/origin/main".into()),
8062 oid: main,
8063 not_for_merge: false,
8064 force: true,
8065 }]
8066 );
8067 }
8068
8069 #[test]
8070 fn fetch_planner_autofollows_tags_and_builds_fetch_head_records() {
8071 let commit = ObjectId::from_hex(
8072 ObjectFormat::Sha1,
8073 "1111111111111111111111111111111111111111",
8074 )
8075 .expect("test operation should succeed");
8076 let refs = vec![
8077 RefAdvertisement {
8078 oid: commit.clone(),
8079 name: "refs/heads/main".into(),
8080 capabilities: Vec::new(),
8081 },
8082 RefAdvertisement {
8083 oid: commit.clone(),
8084 name: "refs/tags/v1".into(),
8085 capabilities: Vec::new(),
8086 },
8087 ];
8088 let refspecs = vec![
8089 parse_refspec("refs/heads/main:refs/heads/main")
8090 .expect("test operation should succeed"),
8091 ];
8092 let updates =
8093 plan_fetch_ref_updates(&refs, &refspecs, true).expect("test operation should succeed");
8094 assert_eq!(
8095 updates,
8096 vec![
8097 FetchRefUpdate {
8098 src: "refs/heads/main".into(),
8099 dst: Some("refs/heads/main".into()),
8100 oid: commit.clone(),
8101 not_for_merge: false,
8102 force: false,
8103 },
8104 FetchRefUpdate {
8105 src: "refs/tags/v1".into(),
8106 dst: Some("refs/tags/v1".into()),
8107 oid: commit.clone(),
8108 not_for_merge: true,
8109 force: false,
8110 },
8111 ]
8112 );
8113 assert_eq!(
8114 fetch_ref_updates_to_fetch_head(&updates, "../bundle.bdl")
8115 .expect("test operation should succeed"),
8116 vec![
8117 FetchHeadRecord {
8118 oid: commit.clone(),
8119 not_for_merge: false,
8120 description: "branch 'main' of ../bundle.bdl".into(),
8121 },
8122 FetchHeadRecord {
8123 oid: commit,
8124 not_for_merge: true,
8125 description: "tag 'v1' of ../bundle.bdl".into(),
8126 },
8127 ]
8128 );
8129 }
8130
8131 #[test]
8132 fn fetch_planner_rejects_missing_or_sourceless_refspecs() {
8133 let refs = vec![RefAdvertisement {
8134 oid: ObjectId::from_hex(
8135 ObjectFormat::Sha1,
8136 "1111111111111111111111111111111111111111",
8137 )
8138 .expect("test operation should succeed"),
8139 name: "refs/heads/main".into(),
8140 capabilities: Vec::new(),
8141 }];
8142 assert!(
8143 plan_fetch_ref_updates(
8144 &refs,
8145 &[parse_refspec("refs/heads/missing").expect("test operation should succeed")],
8146 false
8147 )
8148 .is_err()
8149 );
8150 assert!(
8151 plan_fetch_ref_updates(
8152 &refs,
8153 &[parse_refspec(":refs/heads/main").expect("test operation should succeed")],
8154 false
8155 )
8156 .is_err()
8157 );
8158 }
8159
8160 #[test]
8161 fn fetch_planner_sourceless_positive_refspec_returns_err_not_panic() {
8162 let refs = vec![RefAdvertisement {
8167 oid: ObjectId::from_hex(
8168 ObjectFormat::Sha1,
8169 "1111111111111111111111111111111111111111",
8170 )
8171 .expect("test operation should succeed"),
8172 name: "refs/heads/main".into(),
8173 capabilities: Vec::new(),
8174 }];
8175 let malformed = RefSpec {
8176 force: false,
8177 negative: false,
8178 src: None,
8179 dst: Some("refs/heads/main".into()),
8180 pattern: false,
8181 };
8182 let result = plan_fetch_ref_updates(&refs, &[malformed], false);
8183 assert!(
8184 result.is_err(),
8185 "sourceless positive refspec must yield Err, got {result:?}"
8186 );
8187 }
8188
8189 #[test]
8190 fn push_planner_builds_create_update_delete_and_matching_commands() {
8191 let old = ObjectId::from_hex(
8192 ObjectFormat::Sha1,
8193 "1111111111111111111111111111111111111111",
8194 )
8195 .expect("test operation should succeed");
8196 let new = ObjectId::from_hex(
8197 ObjectFormat::Sha1,
8198 "2222222222222222222222222222222222222222",
8199 )
8200 .expect("test operation should succeed");
8201 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8202 let local_refs = vec![
8203 PushSourceRef {
8204 oid: new.clone(),
8205 name: "refs/heads/main".into(),
8206 },
8207 PushSourceRef {
8208 oid: new.clone(),
8209 name: "refs/heads/new".into(),
8210 },
8211 ];
8212 let remote_refs = vec![
8213 RefAdvertisement {
8214 oid: old.clone(),
8215 name: "refs/heads/main".into(),
8216 capabilities: Vec::new(),
8217 },
8218 RefAdvertisement {
8219 oid: old.clone(),
8220 name: "refs/heads/old".into(),
8221 capabilities: Vec::new(),
8222 },
8223 ];
8224
8225 assert_eq!(
8226 plan_push_commands(
8227 ObjectFormat::Sha1,
8228 &local_refs,
8229 &remote_refs,
8230 &[parse_refspec("refs/heads/main").expect("test operation should succeed")],
8231 )
8232 .expect("test operation should succeed"),
8233 vec![ReceivePackCommand {
8234 old_id: old.clone(),
8235 new_id: new.clone(),
8236 name: "refs/heads/main".into(),
8237 }]
8238 );
8239 assert_eq!(
8240 plan_push_commands(
8241 ObjectFormat::Sha1,
8242 &local_refs,
8243 &remote_refs,
8244 &[parse_refspec("refs/heads/new:refs/heads/new")
8245 .expect("test operation should succeed")],
8246 )
8247 .expect("test operation should succeed"),
8248 vec![ReceivePackCommand {
8249 old_id: zero.clone(),
8250 new_id: new.clone(),
8251 name: "refs/heads/new".into(),
8252 }]
8253 );
8254 assert_eq!(
8255 plan_push_commands(
8256 ObjectFormat::Sha1,
8257 &local_refs,
8258 &remote_refs,
8259 &[parse_refspec(":refs/heads/old").expect("test operation should succeed")],
8260 )
8261 .expect("test operation should succeed"),
8262 vec![ReceivePackCommand {
8263 old_id: old.clone(),
8264 new_id: zero,
8265 name: "refs/heads/old".into(),
8266 }]
8267 );
8268 assert_eq!(
8269 plan_push_commands(
8270 ObjectFormat::Sha1,
8271 &local_refs,
8272 &remote_refs,
8273 &[parse_refspec(":").expect("test operation should succeed")],
8274 )
8275 .expect("test operation should succeed"),
8276 vec![ReceivePackCommand {
8277 old_id: old,
8278 new_id: new,
8279 name: "refs/heads/main".into(),
8280 }]
8281 );
8282 }
8283
8284 #[test]
8285 fn push_planner_builds_wildcard_commands_and_rejects_bad_refspecs() {
8286 let new = ObjectId::from_hex(
8287 ObjectFormat::Sha1,
8288 "2222222222222222222222222222222222222222",
8289 )
8290 .expect("test operation should succeed");
8291 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8292 let local_refs = vec![PushSourceRef {
8293 oid: new.clone(),
8294 name: "refs/heads/topic".into(),
8295 }];
8296 let commands = plan_push_commands(
8297 ObjectFormat::Sha1,
8298 &local_refs,
8299 &[],
8300 &[parse_refspec("refs/heads/*:refs/heads/review/*")
8301 .expect("test operation should succeed")],
8302 )
8303 .expect("test operation should succeed");
8304 assert_eq!(
8305 commands,
8306 vec![ReceivePackCommand {
8307 old_id: zero,
8308 new_id: new,
8309 name: "refs/heads/review/topic".into(),
8310 }]
8311 );
8312 assert!(
8313 plan_push_commands(
8314 ObjectFormat::Sha1,
8315 &local_refs,
8316 &[],
8317 &[parse_refspec("^refs/heads/topic").expect("test operation should succeed")],
8318 )
8319 .is_err()
8320 );
8321 assert_eq!(
8322 plan_push_commands(
8323 ObjectFormat::Sha1,
8324 &local_refs,
8325 &[],
8326 &[parse_refspec(":refs/heads/missing").expect("test operation should succeed")],
8327 )
8328 .expect("missing deletes are sent as zero-to-zero commands"),
8329 vec![ReceivePackCommand {
8330 old_id: zero,
8331 new_id: zero,
8332 name: "refs/heads/missing".into(),
8333 }]
8334 );
8335 }
8336
8337 #[test]
8338 fn receive_pack_push_request_builder_negotiates_capabilities() {
8339 let old_id = ObjectId::from_hex(
8340 ObjectFormat::Sha1,
8341 "1111111111111111111111111111111111111111",
8342 )
8343 .expect("test operation should succeed");
8344 let new_id = ObjectId::from_hex(
8345 ObjectFormat::Sha1,
8346 "2222222222222222222222222222222222222222",
8347 )
8348 .expect("test operation should succeed");
8349 let features = ReceivePackFeatures {
8350 report_status_v2: true,
8351 atomic: true,
8352 ofs_delta: true,
8353 push_options: true,
8354 side_band_64k: true,
8355 quiet: true,
8356 object_format: Some(ObjectFormat::Sha1),
8357 ..ReceivePackFeatures::default()
8358 };
8359 let request = build_receive_pack_push_request(
8360 &features,
8361 vec![ReceivePackCommand {
8362 old_id,
8363 new_id,
8364 name: "refs/heads/main".into(),
8365 }],
8366 b"PACKdata".to_vec(),
8367 ReceivePackPushRequestOptions {
8368 report_status_v2: true,
8369 atomic: true,
8370 ofs_delta: true,
8371 side_band_64k: true,
8372 quiet: true,
8373 agent: Some("sley/0".into()),
8374 object_format: Some(ObjectFormat::Sha1),
8375 push_options: vec!["ci.skip".into()],
8376 ..ReceivePackPushRequestOptions::default()
8377 },
8378 )
8379 .expect("test operation should succeed");
8380 assert_eq!(
8381 request.commands.capabilities,
8382 vec![
8383 Capability {
8384 name: "report-status-v2".into(),
8385 value: None,
8386 },
8387 Capability {
8388 name: "atomic".into(),
8389 value: None,
8390 },
8391 Capability {
8392 name: "ofs-delta".into(),
8393 value: None,
8394 },
8395 Capability {
8396 name: "side-band-64k".into(),
8397 value: None,
8398 },
8399 Capability {
8400 name: "quiet".into(),
8401 value: None,
8402 },
8403 Capability {
8404 name: "agent".into(),
8405 value: Some("sley/0".into()),
8406 },
8407 Capability {
8408 name: "object-format".into(),
8409 value: Some("sha1".into()),
8410 },
8411 Capability {
8412 name: "push-options".into(),
8413 value: None,
8414 },
8415 ]
8416 );
8417 assert_eq!(request.push_options, Some(vec!["ci.skip".into()]));
8418 validate_receive_pack_push_request_features(&features, &request)
8419 .expect("test operation should succeed");
8420 }
8421
8422 #[test]
8423 fn receive_pack_push_request_builder_handles_delete_only_and_rejects_unadvertised() {
8424 let old_id = ObjectId::from_hex(
8425 ObjectFormat::Sha1,
8426 "1111111111111111111111111111111111111111",
8427 )
8428 .expect("test operation should succeed");
8429 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8430 let features = ReceivePackFeatures {
8431 delete_refs: true,
8432 ..ReceivePackFeatures::default()
8433 };
8434 let request = build_receive_pack_push_request(
8435 &features,
8436 vec![ReceivePackCommand {
8437 old_id,
8438 new_id: zero,
8439 name: "refs/heads/old".into(),
8440 }],
8441 Vec::new(),
8442 ReceivePackPushRequestOptions::default(),
8443 )
8444 .expect("test operation should succeed");
8445 assert_eq!(
8446 request.commands.capabilities,
8447 vec![Capability {
8448 name: "delete-refs".into(),
8449 value: None,
8450 }]
8451 );
8452 assert!(request.packfile.is_empty());
8453
8454 assert!(
8455 build_receive_pack_push_request(
8456 &ReceivePackFeatures::default(),
8457 request.commands.commands.clone(),
8458 Vec::new(),
8459 ReceivePackPushRequestOptions::default(),
8460 )
8461 .is_err()
8462 );
8463 assert!(
8464 build_receive_pack_push_request(
8465 &features,
8466 request.commands.commands,
8467 b"PACK".to_vec(),
8468 ReceivePackPushRequestOptions::default(),
8469 )
8470 .is_err()
8471 );
8472 assert!(
8473 build_receive_pack_push_request(
8474 &features,
8475 Vec::new(),
8476 Vec::new(),
8477 ReceivePackPushRequestOptions {
8478 push_options: vec!["ci.skip".into()],
8479 ..ReceivePackPushRequestOptions::default()
8480 },
8481 )
8482 .is_err()
8483 );
8484 }
8485
8486 #[test]
8487 fn smart_http_helpers_build_paths_and_content_types() {
8488 let sha1 = ObjectId::from_hex(
8489 ObjectFormat::Sha1,
8490 "1111111111111111111111111111111111111111",
8491 )
8492 .expect("test operation should succeed");
8493 let sha256 = ObjectId::from_hex(
8494 ObjectFormat::Sha256,
8495 "2222222222222222222222222222222222222222222222222222222222222222",
8496 )
8497 .expect("test operation should succeed");
8498 assert_eq!(
8499 smart_http_info_refs_path("/repo.git/", GitService::UploadPack)
8500 .expect("test operation should succeed"),
8501 "/repo.git/info/refs?service=git-upload-pack"
8502 );
8503 assert_eq!(
8504 dumb_http_info_refs_path("/repo.git/").expect("test operation should succeed"),
8505 "/repo.git/info/refs"
8506 );
8507 assert_eq!(
8508 dumb_http_alternates_path("/repo.git").expect("test operation should succeed"),
8509 "/repo.git/objects/info/http-alternates"
8510 );
8511 assert_eq!(
8512 dumb_http_packs_path("/repo.git/").expect("test operation should succeed"),
8513 "/repo.git/objects/info/packs"
8514 );
8515 assert_eq!(
8516 dumb_http_loose_object_path("/repo.git/", &sha1)
8517 .expect("test operation should succeed"),
8518 "/repo.git/objects/11/11111111111111111111111111111111111111"
8519 );
8520 assert_eq!(
8521 dumb_http_loose_object_path("/repo.git/", &sha256)
8522 .expect("test operation should succeed"),
8523 "/repo.git/objects/22/22222222222222222222222222222222222222222222222222222222222222"
8524 );
8525 assert_eq!(
8526 dumb_http_pack_file_path("/repo.git/", &sha1).expect("test operation should succeed"),
8527 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.pack"
8528 );
8529 assert_eq!(
8530 dumb_http_pack_index_path("/repo.git/", &sha1).expect("test operation should succeed"),
8531 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.idx"
8532 );
8533 assert_eq!(
8534 smart_http_rpc_path("/repo.git", GitService::ReceivePack)
8535 .expect("test operation should succeed"),
8536 "/repo.git/git-receive-pack"
8537 );
8538 assert_eq!(
8539 smart_http_advertisement_content_type(GitService::UploadPack)
8540 .expect("test operation should succeed"),
8541 "application/x-git-upload-pack-advertisement"
8542 );
8543 assert_eq!(
8544 smart_http_rpc_request_content_type(GitService::UploadPack)
8545 .expect("test operation should succeed"),
8546 "application/x-git-upload-pack-request"
8547 );
8548 assert_eq!(
8549 smart_http_rpc_result_content_type(GitService::ReceivePack)
8550 .expect("test operation should succeed"),
8551 "application/x-git-receive-pack-result"
8552 );
8553 assert_eq!(
8554 parse_smart_http_advertisement_content_type(
8555 "Application/X-Git-Upload-Pack-Advertisement"
8556 )
8557 .expect("test operation should succeed"),
8558 GitService::UploadPack
8559 );
8560 assert_eq!(
8561 parse_smart_http_rpc_request_content_type("application/x-git-receive-pack-request")
8562 .expect("test operation should succeed"),
8563 GitService::ReceivePack
8564 );
8565 assert_eq!(
8566 parse_smart_http_rpc_result_content_type("application/x-git-upload-pack-result")
8567 .expect("test operation should succeed"),
8568 GitService::UploadPack
8569 );
8570 }
8571
8572 #[test]
8573 fn smart_http_helpers_reject_invalid_services_paths_and_content_types() {
8574 let oid = ObjectId::from_hex(
8575 ObjectFormat::Sha1,
8576 "1111111111111111111111111111111111111111",
8577 )
8578 .expect("test operation should succeed");
8579 assert!(smart_http_info_refs_path("repo.git", GitService::UploadPack).is_err());
8580 assert!(smart_http_rpc_path("/repo.git?x=1", GitService::UploadPack).is_err());
8581 assert!(dumb_http_info_refs_path("repo.git").is_err());
8582 assert!(dumb_http_alternates_path("/repo.git#fragment").is_err());
8583 assert!(dumb_http_packs_path("/repo.git?query").is_err());
8584 assert!(dumb_http_loose_object_path("repo.git", &oid).is_err());
8585 assert!(dumb_http_pack_file_path("/repo.git#fragment", &oid).is_err());
8586 assert!(dumb_http_pack_index_path("/repo.git?query", &oid).is_err());
8587 assert!(smart_http_info_refs_path("/repo.git", GitService::UploadArchive).is_err());
8588 assert!(smart_http_advertisement_content_type(GitService::UploadArchive).is_err());
8589 assert!(
8590 parse_smart_http_advertisement_content_type(
8591 "application/x-git-upload-archive-advertisement"
8592 )
8593 .is_err()
8594 );
8595 assert!(
8596 parse_smart_http_rpc_request_content_type("application/x-git-upload-pack-result")
8597 .is_err()
8598 );
8599 assert!(
8600 parse_smart_http_rpc_result_content_type(
8601 "application/x-git-receive-pack-result; charset=utf-8"
8602 )
8603 .is_err()
8604 );
8605 }
8606
8607 #[test]
8608 fn sideband_packets_parse_and_encode_channels() {
8609 let payloads = vec![
8610 b"\x01PACK bytes".to_vec(),
8611 b"\x02counting objects\n".to_vec(),
8612 b"\x03fatal error\n".to_vec(),
8613 ];
8614 let packets = parse_sideband_packets(&payloads).expect("test operation should succeed");
8615 assert_eq!(
8616 packets,
8617 vec![
8618 SideBandPacket {
8619 channel: SideBandChannel::Data,
8620 data: b"PACK bytes".to_vec(),
8621 },
8622 SideBandPacket {
8623 channel: SideBandChannel::Progress,
8624 data: b"counting objects\n".to_vec(),
8625 },
8626 SideBandPacket {
8627 channel: SideBandChannel::Fatal,
8628 data: b"fatal error\n".to_vec(),
8629 },
8630 ]
8631 );
8632 assert_eq!(
8633 encode_sideband_packets(&packets).expect("test operation should succeed"),
8634 payloads
8635 );
8636 }
8637
8638 #[test]
8639 fn sideband_stream_parses_encodes_and_demuxes_packets() {
8640 let frames = vec![
8641 PktLineFrame::Data(vec![1, b'P', b'A']),
8642 PktLineFrame::Data(vec![2, b'c', b'o', b'u', b'n', b't', b'\n']),
8643 PktLineFrame::Data(vec![1, b'C', b'K']),
8644 PktLineFrame::Flush,
8645 ];
8646 let packets = parse_sideband_stream(&frames).expect("test operation should succeed");
8647 assert_eq!(
8648 packets,
8649 vec![
8650 SideBandPacket {
8651 channel: SideBandChannel::Data,
8652 data: b"PA".to_vec(),
8653 },
8654 SideBandPacket {
8655 channel: SideBandChannel::Progress,
8656 data: b"count\n".to_vec(),
8657 },
8658 SideBandPacket {
8659 channel: SideBandChannel::Data,
8660 data: b"CK".to_vec(),
8661 },
8662 ]
8663 );
8664 assert_eq!(
8665 encode_sideband_stream(&packets).expect("test operation should succeed"),
8666 frames
8667 );
8668 assert_eq!(
8669 demux_sideband_stream(&frames).expect("test operation should succeed"),
8670 SideBandDemux {
8671 data: b"PACK".to_vec(),
8672 progress: vec![b"count\n".to_vec()],
8673 }
8674 );
8675 }
8676
8677 #[test]
8678 fn sideband_stream_reads_and_writes_until_flush() {
8679 let packets = vec![
8680 SideBandPacket {
8681 channel: SideBandChannel::Data,
8682 data: b"PACK".to_vec(),
8683 },
8684 SideBandPacket {
8685 channel: SideBandChannel::Progress,
8686 data: b"done\n".to_vec(),
8687 },
8688 ];
8689 let mut encoded = Vec::new();
8690 write_sideband_stream(&mut encoded, &packets).expect("test operation should succeed");
8691 encoded.extend_from_slice(b"tail");
8692
8693 let mut input = encoded.as_slice();
8694 assert_eq!(
8695 read_sideband_stream(&mut input).expect("test operation should succeed"),
8696 packets
8697 );
8698 assert_eq!(input, b"tail");
8699
8700 let mut input = encoded.as_slice();
8701 assert_eq!(
8702 read_and_demux_sideband_stream(&mut input).expect("test operation should succeed"),
8703 SideBandDemux {
8704 data: b"PACK".to_vec(),
8705 progress: vec![b"done\n".to_vec()],
8706 }
8707 );
8708 assert_eq!(input, b"tail");
8709 }
8710
8711 #[test]
8712 fn sideband_packets_demux_data_and_progress() {
8713 let payloads = vec![
8714 b"\x01PACK".to_vec(),
8715 b"\x02counting objects\n".to_vec(),
8716 b"\x01 bytes".to_vec(),
8717 b"\x02done\n".to_vec(),
8718 ];
8719 assert_eq!(
8720 parse_and_demux_sideband_packets(&payloads).expect("test operation should succeed"),
8721 SideBandDemux {
8722 data: b"PACK bytes".to_vec(),
8723 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
8724 }
8725 );
8726 }
8727
8728 #[test]
8729 fn sideband_packets_reject_bad_channels_and_oversize_payloads() {
8730 assert!(parse_sideband_packet(b"").is_err());
8731 assert!(parse_sideband_packet(b"\x04bad").is_err());
8732 assert!(
8733 parse_sideband_stream(&[PktLineFrame::Data(vec![1, b'P', b'A', b'C', b'K'])]).is_err()
8734 );
8735 assert!(parse_sideband_stream(&[PktLineFrame::Delimiter, PktLineFrame::Flush]).is_err());
8736 assert!(
8737 parse_sideband_stream(&[
8738 PktLineFrame::Data(vec![1, b'P', b'A']),
8739 PktLineFrame::Flush,
8740 PktLineFrame::Data(vec![1, b'C', b'K']),
8741 ])
8742 .is_err()
8743 );
8744 assert!(
8745 parse_sideband_stream(&[
8746 PktLineFrame::Data(vec![1, b'P', b'A']),
8747 PktLineFrame::Data(b"\x04bad".to_vec()),
8748 PktLineFrame::Flush,
8749 ])
8750 .is_err()
8751 );
8752 assert!(
8753 encode_sideband_packet(&SideBandPacket {
8754 channel: SideBandChannel::Data,
8755 data: vec![0; PKT_LINE_MAX_PAYLOAD_LEN],
8756 })
8757 .is_err()
8758 );
8759 assert!(
8760 demux_sideband_packets(&[SideBandPacket {
8761 channel: SideBandChannel::Fatal,
8762 data: b"remote died\n".to_vec(),
8763 }])
8764 .is_err()
8765 );
8766 }
8767
8768 #[test]
8769 fn upload_archive_request_parses_and_encodes_arguments() {
8770 let frames = vec![
8771 PktLineFrame::Data(b"argument --format=tar\n".to_vec()),
8772 PktLineFrame::Data(b"argument HEAD:dir with spaces\n".to_vec()),
8773 PktLineFrame::Flush,
8774 ];
8775 let request = parse_upload_archive_request(&frames).expect("test operation should succeed");
8776 assert_eq!(
8777 request,
8778 UploadArchiveRequest {
8779 arguments: vec!["--format=tar".into(), "HEAD:dir with spaces".into()],
8780 }
8781 );
8782 assert_eq!(
8783 encode_upload_archive_request(&request).expect("test operation should succeed"),
8784 frames
8785 );
8786 }
8787
8788 #[test]
8789 fn upload_archive_request_streams_round_trip() {
8790 let request = UploadArchiveRequest {
8791 arguments: vec!["--prefix=src/".into(), "main".into()],
8792 };
8793 let mut encoded = Vec::new();
8794 write_upload_archive_request(&mut encoded, &request)
8795 .expect("test operation should succeed");
8796 encoded.extend_from_slice(b"tail");
8797
8798 let mut input = encoded.as_slice();
8799 assert_eq!(
8800 read_upload_archive_request(&mut input).expect("test operation should succeed"),
8801 request
8802 );
8803 assert_eq!(input, b"tail");
8804 }
8805
8806 #[test]
8807 fn upload_archive_request_rejects_malformed_streams() {
8808 assert!(parse_upload_archive_request(&[PktLineFrame::Flush]).is_err());
8809 assert!(
8810 parse_upload_archive_request(&[
8811 PktLineFrame::Data(b"--format=tar\n".to_vec()),
8812 PktLineFrame::Flush,
8813 ])
8814 .is_err()
8815 );
8816 assert!(
8817 parse_upload_archive_request(&[
8818 PktLineFrame::Data(b"argument HEAD\n".to_vec()),
8819 PktLineFrame::Delimiter,
8820 PktLineFrame::Flush,
8821 ])
8822 .is_err()
8823 );
8824 assert!(
8825 encode_upload_archive_request(&UploadArchiveRequest {
8826 arguments: vec!["bad\narg".into()],
8827 })
8828 .is_err()
8829 );
8830 }
8831
8832 #[test]
8833 fn upload_archive_response_parses_ack_sideband_and_nack() {
8834 let ack_frames = vec![
8835 PktLineFrame::Data(b"ACK\n".to_vec()),
8836 PktLineFrame::Data(b"\x01tar bytes".to_vec()),
8837 PktLineFrame::Data(b"\x02progress\n".to_vec()),
8838 PktLineFrame::Flush,
8839 ];
8840 let response =
8841 parse_upload_archive_response(&ack_frames).expect("test operation should succeed");
8842 assert_eq!(
8843 response,
8844 UploadArchiveResponse::Ack {
8845 sideband: vec![
8846 SideBandPacket {
8847 channel: SideBandChannel::Data,
8848 data: b"tar bytes".to_vec(),
8849 },
8850 SideBandPacket {
8851 channel: SideBandChannel::Progress,
8852 data: b"progress\n".to_vec(),
8853 },
8854 ],
8855 }
8856 );
8857 assert_eq!(
8858 encode_upload_archive_response(&response).expect("test operation should succeed"),
8859 ack_frames
8860 );
8861 assert_eq!(
8862 demux_upload_archive_response(&response).expect("test operation should succeed"),
8863 SideBandDemux {
8864 data: b"tar bytes".to_vec(),
8865 progress: vec![b"progress\n".to_vec()],
8866 }
8867 );
8868
8869 let nack = UploadArchiveResponse::Nack {
8870 message: "unreachable tree".into(),
8871 };
8872 let nack_frames = vec![
8873 PktLineFrame::Data(b"NACK unreachable tree\n".to_vec()),
8874 PktLineFrame::Flush,
8875 ];
8876 assert_eq!(
8877 parse_upload_archive_response(&nack_frames).expect("test operation should succeed"),
8878 nack
8879 );
8880 assert_eq!(
8881 encode_upload_archive_response(&nack).expect("test operation should succeed"),
8882 nack_frames
8883 );
8884 assert!(demux_upload_archive_response(&nack).is_err());
8885 }
8886
8887 #[test]
8888 fn upload_archive_response_streams_round_trip() {
8889 let response = UploadArchiveResponse::Ack {
8890 sideband: vec![SideBandPacket {
8891 channel: SideBandChannel::Data,
8892 data: b"tar bytes".to_vec(),
8893 }],
8894 };
8895 let mut encoded = Vec::new();
8896 write_upload_archive_response(&mut encoded, &response)
8897 .expect("test operation should succeed");
8898 encoded.extend_from_slice(b"tail");
8899
8900 let mut input = encoded.as_slice();
8901 assert_eq!(
8902 read_upload_archive_response(&mut input).expect("test operation should succeed"),
8903 response
8904 );
8905 assert_eq!(input, b"tail");
8906 }
8907
8908 #[test]
8909 fn upload_archive_response_rejects_malformed_streams() {
8910 assert!(parse_upload_archive_response(&[]).is_err());
8911 assert!(
8912 parse_upload_archive_response(&[
8913 PktLineFrame::Data(b"ACK\n".to_vec()),
8914 PktLineFrame::Flush,
8915 PktLineFrame::Data(b"\x01tail".to_vec()),
8916 ])
8917 .is_err()
8918 );
8919 assert!(
8920 parse_upload_archive_response(&[
8921 PktLineFrame::Data(b"NACK\n".to_vec()),
8922 PktLineFrame::Flush,
8923 ])
8924 .is_err()
8925 );
8926 assert!(
8927 parse_upload_archive_response(&[
8928 PktLineFrame::Data(b"NACK denied\n".to_vec()),
8929 PktLineFrame::Data(b"\x02extra\n".to_vec()),
8930 PktLineFrame::Flush,
8931 ])
8932 .is_err()
8933 );
8934 assert!(
8935 encode_upload_archive_response(&UploadArchiveResponse::Nack {
8936 message: "bad\nmessage".into(),
8937 })
8938 .is_err()
8939 );
8940 }
8941
8942 #[test]
8943 fn capabilities_parse_and_encode_tokens() {
8944 let capabilities = parse_capabilities(
8945 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main\n",
8946 )
8947 .expect("test operation should succeed");
8948 assert_eq!(
8949 capabilities,
8950 vec![
8951 Capability {
8952 name: "multi_ack".into(),
8953 value: None,
8954 },
8955 Capability {
8956 name: "thin-pack".into(),
8957 value: None,
8958 },
8959 Capability {
8960 name: "agent".into(),
8961 value: Some("git/2.54.0".into()),
8962 },
8963 Capability {
8964 name: "symref".into(),
8965 value: Some("HEAD:refs/heads/main".into()),
8966 },
8967 ]
8968 );
8969 assert_eq!(
8970 encode_capabilities(&capabilities).expect("test operation should succeed"),
8971 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main"
8972 );
8973 }
8974
8975 #[test]
8976 fn capabilities_reject_empty_or_delimited_fields() {
8977 assert!(parse_capabilities(b"multi_ack thin-pack").is_err());
8978 assert!(parse_capabilities(b"agent=").is_err());
8979 assert!(parse_capabilities(b"symref=HEAD:refs/heads/main\nbad").is_err());
8980 assert!(
8981 encode_capabilities(&[Capability {
8982 name: "bad name".into(),
8983 value: None,
8984 }])
8985 .is_err()
8986 );
8987 }
8988
8989 #[test]
8990 fn protocol_v2_object_format_uses_capability_or_defaults_to_sha1() {
8991 assert_eq!(
8992 protocol_v2_object_format(&[]).expect("test operation should succeed"),
8993 ObjectFormat::Sha1
8994 );
8995 assert_eq!(
8996 protocol_v2_object_format(&[Capability {
8997 name: "object-format".into(),
8998 value: Some("sha256".into()),
8999 }])
9000 .expect("test operation should succeed"),
9001 ObjectFormat::Sha256
9002 );
9003 assert!(
9004 protocol_v2_object_format(&[Capability {
9005 name: "object-format".into(),
9006 value: None,
9007 }])
9008 .is_err()
9009 );
9010 assert!(
9011 protocol_v2_object_format(&[
9012 Capability {
9013 name: "object-format".into(),
9014 value: Some("sha1".into()),
9015 },
9016 Capability {
9017 name: "object-format".into(),
9018 value: Some("sha256".into()),
9019 },
9020 ])
9021 .is_err()
9022 );
9023 assert!(
9024 protocol_v2_object_format(&[Capability {
9025 name: "object-format".into(),
9026 value: Some("unknown".into()),
9027 }])
9028 .is_err()
9029 );
9030 }
9031
9032 #[test]
9033 fn protocol_v2_command_request_capabilities_validate_against_handshake() {
9034 let handshake = TransportHandshake {
9035 protocol: ProtocolVersion::V2,
9036 capabilities: vec![
9037 Capability {
9038 name: "fetch".into(),
9039 value: Some("shallow filter".into()),
9040 },
9041 Capability {
9042 name: "agent".into(),
9043 value: Some("sley/0".into()),
9044 },
9045 Capability {
9046 name: "object-format".into(),
9047 value: Some("sha1".into()),
9048 },
9049 ],
9050 };
9051 validate_protocol_v2_command_request_capabilities(
9052 &handshake,
9053 &ProtocolV2CommandRequest {
9054 command: "fetch".into(),
9055 capabilities: vec![
9056 Capability {
9057 name: "agent".into(),
9058 value: Some("client/1".into()),
9059 },
9060 Capability {
9061 name: "object-format".into(),
9062 value: Some("sha1".into()),
9063 },
9064 ],
9065 arguments: Vec::new(),
9066 },
9067 )
9068 .expect("test operation should succeed");
9069 assert!(
9070 validate_protocol_v2_command_request_capabilities(
9071 &handshake,
9072 &ProtocolV2CommandRequest {
9073 command: "ls-refs".into(),
9074 capabilities: Vec::new(),
9075 arguments: Vec::new(),
9076 },
9077 )
9078 .is_err()
9079 );
9080 assert!(
9081 validate_protocol_v2_command_request_capabilities(
9082 &handshake,
9083 &ProtocolV2CommandRequest {
9084 command: "fetch".into(),
9085 capabilities: vec![Capability {
9086 name: "server-option".into(),
9087 value: None,
9088 }],
9089 arguments: Vec::new(),
9090 },
9091 )
9092 .is_err()
9093 );
9094 assert!(
9095 validate_protocol_v2_command_request_capabilities(
9096 &handshake,
9097 &ProtocolV2CommandRequest {
9098 command: "fetch".into(),
9099 capabilities: vec![Capability {
9100 name: "object-format".into(),
9101 value: Some("sha256".into()),
9102 }],
9103 arguments: Vec::new(),
9104 },
9105 )
9106 .is_err()
9107 );
9108 assert!(
9109 validate_protocol_v2_command_request_capabilities(
9110 &handshake,
9111 &ProtocolV2CommandRequest {
9112 command: "fetch".into(),
9113 capabilities: vec![Capability {
9114 name: "agent".into(),
9115 value: None,
9116 }],
9117 arguments: Vec::new(),
9118 },
9119 )
9120 .is_err()
9121 );
9122 }
9123
9124 #[test]
9125 fn protocol_v2_command_options_parse_and_encode_known_capabilities() {
9126 let capabilities = vec![
9127 Capability {
9128 name: "agent".into(),
9129 value: Some("sley/0".into()),
9130 },
9131 Capability {
9132 name: "object-format".into(),
9133 value: Some("sha256".into()),
9134 },
9135 Capability {
9136 name: "server-option".into(),
9137 value: Some("trace=true".into()),
9138 },
9139 Capability {
9140 name: "server-option".into(),
9141 value: Some("region=west".into()),
9142 },
9143 Capability {
9144 name: "session-id".into(),
9145 value: Some("abc123".into()),
9146 },
9147 ];
9148 let options = parse_protocol_v2_command_options(&capabilities)
9149 .expect("test operation should succeed");
9150 assert_eq!(
9151 options,
9152 ProtocolV2CommandOptions {
9153 agent: Some("sley/0".into()),
9154 object_format: Some(ObjectFormat::Sha256),
9155 server_options: vec!["trace=true".into(), "region=west".into()],
9156 extra: vec![Capability {
9157 name: "session-id".into(),
9158 value: Some("abc123".into()),
9159 }],
9160 }
9161 );
9162 assert_eq!(
9163 encode_protocol_v2_command_options(&options).expect("test operation should succeed"),
9164 capabilities
9165 );
9166 }
9167
9168 #[test]
9169 fn protocol_v2_command_options_reject_malformed_known_capabilities() {
9170 assert!(
9171 parse_protocol_v2_command_options(&[
9172 Capability {
9173 name: "agent".into(),
9174 value: Some("sley/0".into()),
9175 },
9176 Capability {
9177 name: "agent".into(),
9178 value: Some("sley/1".into()),
9179 },
9180 ])
9181 .is_err()
9182 );
9183 assert!(
9184 parse_protocol_v2_command_options(&[Capability {
9185 name: "object-format".into(),
9186 value: Some("sha512".into()),
9187 }])
9188 .is_err()
9189 );
9190 assert!(
9191 parse_protocol_v2_command_options(&[Capability {
9192 name: "server-option".into(),
9193 value: None,
9194 }])
9195 .is_err()
9196 );
9197 assert!(
9198 encode_protocol_v2_command_options(&ProtocolV2CommandOptions {
9199 extra: vec![Capability {
9200 name: "server-option".into(),
9201 value: Some("trace=true".into()),
9202 }],
9203 ..ProtocolV2CommandOptions::default()
9204 })
9205 .is_err()
9206 );
9207 }
9208
9209 #[test]
9210 fn protocol_v2_ls_refs_features_parse_and_encode_advertisement() {
9211 let capabilities = vec![Capability {
9212 name: "ls-refs".into(),
9213 value: Some("unborn custom".into()),
9214 }];
9215 let features = parse_protocol_v2_ls_refs_features(&capabilities)
9216 .expect("test operation should succeed")
9217 .expect("test operation should succeed");
9218 assert_eq!(
9219 features,
9220 ProtocolV2LsRefsFeatures {
9221 unborn: true,
9222 unknown: vec!["custom".into()],
9223 }
9224 );
9225 assert_eq!(
9226 encode_protocol_v2_ls_refs_capability(&features)
9227 .expect("test operation should succeed"),
9228 capabilities[0]
9229 );
9230 assert_eq!(
9231 parse_protocol_v2_ls_refs_features(&[Capability {
9232 name: "ls-refs".into(),
9233 value: None,
9234 }])
9235 .expect("test operation should succeed")
9236 .expect("test operation should succeed"),
9237 ProtocolV2LsRefsFeatures::default()
9238 );
9239 assert!(
9240 parse_protocol_v2_ls_refs_features(&[Capability {
9241 name: "fetch".into(),
9242 value: Some("filter".into()),
9243 }])
9244 .expect("test operation should succeed")
9245 .is_none()
9246 );
9247 }
9248
9249 #[test]
9250 fn protocol_v2_ls_refs_features_reject_malformed_advertisements() {
9251 assert!(
9252 parse_protocol_v2_ls_refs_features(&[
9253 Capability {
9254 name: "ls-refs".into(),
9255 value: None,
9256 },
9257 Capability {
9258 name: "ls-refs".into(),
9259 value: None,
9260 },
9261 ])
9262 .is_err()
9263 );
9264 assert!(
9265 parse_protocol_v2_ls_refs_features(&[Capability {
9266 name: "ls-refs".into(),
9267 value: Some("unborn custom".into()),
9268 }])
9269 .is_err()
9270 );
9271 assert!(
9272 encode_protocol_v2_ls_refs_capability(&ProtocolV2LsRefsFeatures {
9273 unknown: vec!["unborn".into()],
9274 ..ProtocolV2LsRefsFeatures::default()
9275 })
9276 .is_err()
9277 );
9278 }
9279
9280 #[test]
9281 fn protocol_v2_ls_refs_command_request_validates_unborn_feature() {
9282 let handshake = TransportHandshake {
9283 protocol: ProtocolVersion::V2,
9284 capabilities: vec![Capability {
9285 name: "ls-refs".into(),
9286 value: Some("unborn".into()),
9287 }],
9288 };
9289 let request = ProtocolV2CommandRequest {
9290 command: "ls-refs".into(),
9291 capabilities: Vec::new(),
9292 arguments: vec![b"unborn".to_vec(), b"ref-prefix HEAD".to_vec()],
9293 };
9294 let parsed = validate_protocol_v2_ls_refs_command_request(&handshake, &request)
9295 .expect("test operation should succeed");
9296 assert!(parsed.unborn);
9297 assert_eq!(parsed.ref_prefixes, vec!["HEAD"]);
9298
9299 let blocked = TransportHandshake {
9300 protocol: ProtocolVersion::V2,
9301 capabilities: vec![Capability {
9302 name: "ls-refs".into(),
9303 value: None,
9304 }],
9305 };
9306 assert!(validate_protocol_v2_ls_refs_command_request(&blocked, &request).is_err());
9307 }
9308
9309 #[test]
9310 fn protocol_v2_fetch_features_parse_and_encode_advertisement() {
9311 let capabilities = vec![Capability {
9312 name: "fetch".into(),
9313 value: Some(
9314 "shallow wait-for-done filter ref-in-want sideband-all packfile-uris custom".into(),
9315 ),
9316 }];
9317 let features = parse_protocol_v2_fetch_features(&capabilities)
9318 .expect("test operation should succeed")
9319 .expect("test operation should succeed");
9320 assert_eq!(
9321 features,
9322 ProtocolV2FetchFeatures {
9323 shallow: true,
9324 wait_for_done: true,
9325 filter: true,
9326 ref_in_want: true,
9327 sideband_all: true,
9328 packfile_uris: true,
9329 unknown: vec!["custom".into()],
9330 }
9331 );
9332 assert_eq!(
9333 encode_protocol_v2_fetch_capability(&features).expect("test operation should succeed"),
9334 capabilities[0]
9335 );
9336 assert_eq!(
9337 parse_protocol_v2_fetch_features(&[Capability {
9338 name: "fetch".into(),
9339 value: None,
9340 }])
9341 .expect("test operation should succeed")
9342 .expect("test operation should succeed"),
9343 ProtocolV2FetchFeatures::default()
9344 );
9345 assert!(
9346 parse_protocol_v2_fetch_features(&[])
9347 .expect("test operation should succeed")
9348 .is_none()
9349 );
9350 }
9351
9352 #[test]
9353 fn protocol_v2_fetch_features_reject_malformed_advertisements() {
9354 assert!(
9355 parse_protocol_v2_fetch_features(&[
9356 Capability {
9357 name: "fetch".into(),
9358 value: None,
9359 },
9360 Capability {
9361 name: "fetch".into(),
9362 value: None,
9363 },
9364 ])
9365 .is_err()
9366 );
9367 assert!(
9368 parse_protocol_v2_fetch_features(&[Capability {
9369 name: "fetch".into(),
9370 value: Some("filter shallow".into()),
9371 }])
9372 .is_err()
9373 );
9374 assert!(
9375 encode_protocol_v2_fetch_capability(&ProtocolV2FetchFeatures {
9376 unknown: vec!["filter".into()],
9377 ..ProtocolV2FetchFeatures::default()
9378 })
9379 .is_err()
9380 );
9381 }
9382
9383 #[test]
9384 fn protocol_v2_fetch_request_features_validate_feature_gated_arguments() {
9385 let features = ProtocolV2FetchFeatures {
9386 shallow: true,
9387 wait_for_done: true,
9388 filter: true,
9389 ref_in_want: true,
9390 sideband_all: true,
9391 packfile_uris: true,
9392 unknown: Vec::new(),
9393 };
9394 validate_protocol_v2_fetch_request_features(
9395 &features,
9396 &ProtocolV2FetchRequest {
9397 want_refs: vec!["refs/heads/main".into()],
9398 shallow: vec![
9399 ObjectId::from_hex(
9400 ObjectFormat::Sha1,
9401 "1111111111111111111111111111111111111111",
9402 )
9403 .expect("test operation should succeed"),
9404 ],
9405 deepen: Some(1),
9406 filter: Some("blob:none".into()),
9407 packfile_uris: Some("https".into()),
9408 sideband_all: true,
9409 wait_for_done: true,
9410 ..ProtocolV2FetchRequest::default()
9411 },
9412 )
9413 .expect("test operation should succeed");
9414
9415 let request = ProtocolV2FetchRequest {
9416 want_refs: vec!["refs/heads/main".into()],
9417 filter: Some("blob:none".into()),
9418 sideband_all: true,
9419 ..ProtocolV2FetchRequest::default()
9420 };
9421 assert!(
9422 validate_protocol_v2_fetch_request_features(
9423 &ProtocolV2FetchFeatures::default(),
9424 &request,
9425 )
9426 .is_err()
9427 );
9428 assert!(
9429 validate_protocol_v2_fetch_request_features(
9430 &ProtocolV2FetchFeatures {
9431 ref_in_want: true,
9432 filter: true,
9433 ..ProtocolV2FetchFeatures::default()
9434 },
9435 &request,
9436 )
9437 .is_err()
9438 );
9439 }
9440
9441 #[test]
9442 fn protocol_v2_fetch_command_request_validates_against_handshake_features() {
9443 let handshake = TransportHandshake {
9444 protocol: ProtocolVersion::V2,
9445 capabilities: vec![
9446 Capability {
9447 name: "fetch".into(),
9448 value: Some("filter ref-in-want".into()),
9449 },
9450 Capability {
9451 name: "agent".into(),
9452 value: Some("sley/0".into()),
9453 },
9454 ],
9455 };
9456 let request = ProtocolV2CommandRequest {
9457 command: "fetch".into(),
9458 capabilities: vec![Capability {
9459 name: "agent".into(),
9460 value: Some("client/1".into()),
9461 }],
9462 arguments: vec![
9463 b"want-ref refs/heads/main".to_vec(),
9464 b"filter blob:none".to_vec(),
9465 ],
9466 };
9467 let fetch =
9468 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &request)
9469 .expect("test operation should succeed");
9470 assert_eq!(fetch.want_refs, vec!["refs/heads/main"]);
9471 assert_eq!(fetch.filter.as_deref(), Some("blob:none"));
9472
9473 let mut bad = request.clone();
9474 bad.arguments.push(b"sideband-all".to_vec());
9475 assert!(
9476 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &bad)
9477 .is_err()
9478 );
9479 }
9480
9481 #[test]
9482 fn protocol_v2_object_info_request_parses_encodes_and_validates() {
9483 let oid = ObjectId::from_hex(
9484 ObjectFormat::Sha1,
9485 "1111111111111111111111111111111111111111",
9486 )
9487 .expect("test operation should succeed");
9488 let request = ProtocolV2CommandRequest {
9489 command: "object-info".into(),
9490 capabilities: Vec::new(),
9491 arguments: vec![
9492 b"size".to_vec(),
9493 b"oid 1111111111111111111111111111111111111111".to_vec(),
9494 ],
9495 };
9496 let parsed =
9497 ProtocolV2ObjectInfoRequest::from_command_request(ObjectFormat::Sha1, &request)
9498 .expect("test operation should succeed");
9499 assert_eq!(
9500 parsed,
9501 ProtocolV2ObjectInfoRequest {
9502 size: true,
9503 oids: vec![oid],
9504 }
9505 );
9506 assert_eq!(
9507 parsed
9508 .to_command_request()
9509 .expect("test operation should succeed"),
9510 request
9511 );
9512
9513 let handshake = TransportHandshake {
9514 protocol: ProtocolVersion::V2,
9515 capabilities: vec![Capability {
9516 name: "object-info".into(),
9517 value: None,
9518 }],
9519 };
9520 assert_eq!(
9521 validate_protocol_v2_object_info_command_request(
9522 &handshake,
9523 ObjectFormat::Sha1,
9524 &request,
9525 )
9526 .expect("test operation should succeed"),
9527 parsed
9528 );
9529 }
9530
9531 #[test]
9532 fn protocol_v2_object_info_request_streams_round_trip() {
9533 let request = ProtocolV2ObjectInfoRequest {
9534 size: true,
9535 oids: vec![
9536 ObjectId::from_hex(
9537 ObjectFormat::Sha1,
9538 "1111111111111111111111111111111111111111",
9539 )
9540 .expect("test operation should succeed"),
9541 ],
9542 };
9543 let mut encoded = Vec::new();
9544 write_protocol_v2_object_info_request(&mut encoded, &request)
9545 .expect("test operation should succeed");
9546 encoded.extend_from_slice(b"tail");
9547
9548 let mut input = encoded.as_slice();
9549 assert_eq!(
9550 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut input)
9551 .expect("test operation should succeed"),
9552 request
9553 );
9554 assert_eq!(input, b"tail");
9555 }
9556
9557 #[test]
9558 fn protocol_v2_object_info_request_rejects_malformed_arguments() {
9559 assert!(
9560 ProtocolV2ObjectInfoRequest::from_command_request(
9561 ObjectFormat::Sha1,
9562 &ProtocolV2CommandRequest {
9563 command: "object-info".into(),
9564 capabilities: Vec::new(),
9565 arguments: vec![b"oid 1111111111111111111111111111111111111111".to_vec()],
9566 },
9567 )
9568 .is_err()
9569 );
9570 assert!(
9571 ProtocolV2ObjectInfoRequest::from_command_request(
9572 ObjectFormat::Sha1,
9573 &ProtocolV2CommandRequest {
9574 command: "object-info".into(),
9575 capabilities: Vec::new(),
9576 arguments: vec![b"size".to_vec(), b"size".to_vec()],
9577 },
9578 )
9579 .is_err()
9580 );
9581 assert!(
9582 ProtocolV2ObjectInfoRequest::from_command_request(
9583 ObjectFormat::Sha1,
9584 &ProtocolV2CommandRequest {
9585 command: "object-info".into(),
9586 capabilities: Vec::new(),
9587 arguments: vec![b"size".to_vec()],
9588 },
9589 )
9590 .is_err()
9591 );
9592 assert!(
9593 ProtocolV2ObjectInfoRequest::from_command_request(
9594 ObjectFormat::Sha1,
9595 &ProtocolV2CommandRequest {
9596 command: "object-info".into(),
9597 capabilities: Vec::new(),
9598 arguments: vec![b"size".to_vec(), b"oid not-an-oid".to_vec()],
9599 },
9600 )
9601 .is_err()
9602 );
9603 assert!(
9604 validate_protocol_v2_object_info_command_request(
9605 &TransportHandshake {
9606 protocol: ProtocolVersion::V2,
9607 capabilities: Vec::new(),
9608 },
9609 ObjectFormat::Sha1,
9610 &ProtocolV2CommandRequest {
9611 command: "object-info".into(),
9612 capabilities: Vec::new(),
9613 arguments: vec![
9614 b"size".to_vec(),
9615 b"oid 1111111111111111111111111111111111111111".to_vec(),
9616 ],
9617 },
9618 )
9619 .is_err()
9620 );
9621 }
9622
9623 #[test]
9624 fn protocol_v2_command_request_classifies_known_and_unknown_commands() {
9625 let handshake = TransportHandshake {
9626 protocol: ProtocolVersion::V2,
9627 capabilities: vec![
9628 Capability {
9629 name: "ls-refs".into(),
9630 value: Some("unborn".into()),
9631 },
9632 Capability {
9633 name: "fetch".into(),
9634 value: Some("filter ref-in-want".into()),
9635 },
9636 Capability {
9637 name: "object-info".into(),
9638 value: None,
9639 },
9640 Capability {
9641 name: "server-option".into(),
9642 value: None,
9643 },
9644 Capability {
9645 name: "server-info".into(),
9646 value: Some("custom".into()),
9647 },
9648 ],
9649 };
9650 assert_eq!(
9651 classify_protocol_v2_command_request(
9652 &handshake,
9653 ObjectFormat::Sha1,
9654 &ProtocolV2CommandRequest {
9655 command: "ls-refs".into(),
9656 capabilities: Vec::new(),
9657 arguments: vec![b"unborn".to_vec()],
9658 },
9659 )
9660 .expect("test operation should succeed"),
9661 ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9662 unborn: true,
9663 ..ProtocolV2LsRefsRequest::default()
9664 })
9665 );
9666 assert_eq!(
9667 classify_protocol_v2_command_request(
9668 &handshake,
9669 ObjectFormat::Sha1,
9670 &ProtocolV2CommandRequest {
9671 command: "fetch".into(),
9672 capabilities: Vec::new(),
9673 arguments: vec![
9674 b"want-ref refs/heads/main".to_vec(),
9675 b"filter blob:none".to_vec(),
9676 ],
9677 },
9678 )
9679 .expect("test operation should succeed"),
9680 ProtocolV2Command::Fetch(ProtocolV2FetchRequest {
9681 want_refs: vec!["refs/heads/main".into()],
9682 filter: Some("blob:none".into()),
9683 ..ProtocolV2FetchRequest::default()
9684 })
9685 );
9686 assert_eq!(
9687 classify_protocol_v2_command_request(
9688 &handshake,
9689 ObjectFormat::Sha1,
9690 &ProtocolV2CommandRequest {
9691 command: "object-info".into(),
9692 capabilities: Vec::new(),
9693 arguments: vec![
9694 b"size".to_vec(),
9695 b"oid 1111111111111111111111111111111111111111".to_vec(),
9696 ],
9697 },
9698 )
9699 .expect("test operation should succeed"),
9700 ProtocolV2Command::ObjectInfo(ProtocolV2ObjectInfoRequest {
9701 size: true,
9702 oids: vec![
9703 ObjectId::from_hex(
9704 ObjectFormat::Sha1,
9705 "1111111111111111111111111111111111111111",
9706 )
9707 .expect("test operation should succeed")
9708 ],
9709 })
9710 );
9711
9712 let unknown = ProtocolV2CommandRequest {
9713 command: "server-info".into(),
9714 capabilities: vec![Capability {
9715 name: "server-option".into(),
9716 value: Some("trace=true".into()),
9717 }],
9718 arguments: Vec::new(),
9719 };
9720 assert_eq!(
9721 classify_protocol_v2_command_request(&handshake, ObjectFormat::Sha1, &unknown)
9722 .expect("test operation should succeed"),
9723 ProtocolV2Command::Unknown(unknown)
9724 );
9725 assert!(
9726 classify_protocol_v2_command_request(
9727 &handshake,
9728 ObjectFormat::Sha1,
9729 &ProtocolV2CommandRequest {
9730 command: "not-advertised".into(),
9731 capabilities: Vec::new(),
9732 arguments: Vec::new(),
9733 },
9734 )
9735 .is_err()
9736 );
9737 }
9738
9739 #[test]
9740 fn protocol_v2_session_request_classifies_streamed_command_and_done() {
9741 let handshake = TransportHandshake {
9742 protocol: ProtocolVersion::V2,
9743 capabilities: vec![
9744 Capability {
9745 name: "ls-refs".into(),
9746 value: Some("unborn".into()),
9747 },
9748 Capability {
9749 name: "fetch".into(),
9750 value: Some("filter ref-in-want".into()),
9751 },
9752 ],
9753 };
9754 let command = ProtocolV2Request::Command(ProtocolV2CommandRequest {
9755 command: "ls-refs".into(),
9756 capabilities: Vec::new(),
9757 arguments: vec![b"unborn".to_vec()],
9758 });
9759 assert_eq!(
9760 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &command)
9761 .expect("test operation should succeed"),
9762 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9763 unborn: true,
9764 ..ProtocolV2LsRefsRequest::default()
9765 }))
9766 );
9767 assert_eq!(
9768 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &ProtocolV2Request::Done)
9769 .expect("test operation should succeed"),
9770 ProtocolV2SessionRequest::Done
9771 );
9772
9773 let mut encoded = Vec::new();
9774 write_protocol_v2_request(&mut encoded, &command).expect("test operation should succeed");
9775 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
9776 .expect("test operation should succeed");
9777 encoded.extend_from_slice(b"tail");
9778
9779 let mut input = encoded.as_slice();
9780 assert_eq!(
9781 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9782 .expect("test operation should succeed"),
9783 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9784 unborn: true,
9785 ..ProtocolV2LsRefsRequest::default()
9786 }))
9787 );
9788 assert_eq!(
9789 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9790 .expect("test operation should succeed"),
9791 ProtocolV2SessionRequest::Done
9792 );
9793 assert_eq!(input, b"tail");
9794 }
9795
9796 #[test]
9797 fn advertised_ref_parses_first_v0_capability_line() {
9798 let payload =
9799 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 HEAD\0multi_ack symref=HEAD:refs/heads/main\n";
9800 let advertisement = parse_ref_advertisement(ObjectFormat::Sha1, payload)
9801 .expect("test operation should succeed");
9802 assert_eq!(
9803 advertisement.oid,
9804 ObjectId::from_hex(
9805 ObjectFormat::Sha1,
9806 "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
9807 )
9808 .expect("test operation should succeed")
9809 );
9810 assert_eq!(advertisement.name, "HEAD");
9811 assert_eq!(
9812 advertisement.capabilities,
9813 vec![
9814 Capability {
9815 name: "multi_ack".into(),
9816 value: None,
9817 },
9818 Capability {
9819 name: "symref".into(),
9820 value: Some("HEAD:refs/heads/main".into()),
9821 },
9822 ]
9823 );
9824 }
9825
9826 #[test]
9827 fn advertised_ref_parses_lines_without_capabilities() {
9828 let advertisement = parse_ref_advertisement(
9829 ObjectFormat::Sha1,
9830 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 refs/heads/main\n",
9831 )
9832 .expect("test operation should succeed");
9833 assert_eq!(advertisement.name, "refs/heads/main");
9834 assert!(advertisement.capabilities.is_empty());
9835 }
9836
9837 #[test]
9838 fn advertised_ref_rejects_malformed_payloads() {
9839 assert!(
9840 parse_ref_advertisement(ObjectFormat::Sha1, b"not-an-oid refs/heads/main\n").is_err()
9841 );
9842 assert!(
9843 parse_ref_advertisement(
9844 ObjectFormat::Sha1,
9845 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n"
9846 )
9847 .is_err()
9848 );
9849 }
9850
9851 #[test]
9852 fn advertised_refs_parse_and_encode_stream() {
9853 let main = ObjectId::from_hex(
9854 ObjectFormat::Sha1,
9855 "1111111111111111111111111111111111111111",
9856 )
9857 .expect("test operation should succeed");
9858 let feature = ObjectId::from_hex(
9859 ObjectFormat::Sha1,
9860 "2222222222222222222222222222222222222222",
9861 )
9862 .expect("test operation should succeed");
9863 let frames = vec![
9864 PktLineFrame::Data(
9865 b"1111111111111111111111111111111111111111 HEAD\0multi_ack thin-pack agent=git/2.54.0\n"
9866 .to_vec(),
9867 ),
9868 PktLineFrame::Data(
9869 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9870 ),
9871 PktLineFrame::Flush,
9872 ];
9873 let advertisements = parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9874 .expect("test operation should succeed");
9875 assert_eq!(
9876 advertisements,
9877 vec![
9878 RefAdvertisement {
9879 oid: main,
9880 name: "HEAD".into(),
9881 capabilities: vec![
9882 Capability {
9883 name: "multi_ack".into(),
9884 value: None,
9885 },
9886 Capability {
9887 name: "thin-pack".into(),
9888 value: None,
9889 },
9890 Capability {
9891 name: "agent".into(),
9892 value: Some("git/2.54.0".into()),
9893 },
9894 ],
9895 },
9896 RefAdvertisement {
9897 oid: feature,
9898 name: "refs/heads/feature".into(),
9899 capabilities: Vec::new(),
9900 },
9901 ]
9902 );
9903 assert_eq!(
9904 encode_ref_advertisements(&advertisements).expect("test operation should succeed"),
9905 frames
9906 );
9907 assert_eq!(
9908 parse_ref_advertisements(ObjectFormat::Sha1, &[PktLineFrame::Flush])
9909 .expect("test operation should succeed"),
9910 Vec::<RefAdvertisement>::new()
9911 );
9912 }
9913
9914 #[test]
9915 fn advertised_ref_set_parses_v1_version_refs_and_shallow() {
9916 let main = ObjectId::from_hex(
9917 ObjectFormat::Sha1,
9918 "1111111111111111111111111111111111111111",
9919 )
9920 .expect("test operation should succeed");
9921 let feature = ObjectId::from_hex(
9922 ObjectFormat::Sha1,
9923 "2222222222222222222222222222222222222222",
9924 )
9925 .expect("test operation should succeed");
9926 let shallow = ObjectId::from_hex(
9927 ObjectFormat::Sha1,
9928 "3333333333333333333333333333333333333333",
9929 )
9930 .expect("test operation should succeed");
9931 let frames = vec![
9932 PktLineFrame::Data(b"version 1\n".to_vec()),
9933 PktLineFrame::Data(
9934 b"1111111111111111111111111111111111111111 HEAD\0multi_ack symref=HEAD:refs/heads/main\n"
9935 .to_vec(),
9936 ),
9937 PktLineFrame::Data(
9938 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9939 ),
9940 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
9941 PktLineFrame::Flush,
9942 ];
9943
9944 let set = parse_ref_advertisement_set(ObjectFormat::Sha1, &frames)
9945 .expect("test operation should succeed");
9946 assert_eq!(set.protocol, ProtocolVersion::V1);
9947 assert_eq!(set.shallow, vec![shallow]);
9948 assert_eq!(
9949 set.refs,
9950 vec![
9951 RefAdvertisement {
9952 oid: main,
9953 name: "HEAD".into(),
9954 capabilities: vec![
9955 Capability {
9956 name: "multi_ack".into(),
9957 value: None,
9958 },
9959 Capability {
9960 name: "symref".into(),
9961 value: Some("HEAD:refs/heads/main".into()),
9962 },
9963 ],
9964 },
9965 RefAdvertisement {
9966 oid: feature,
9967 name: "refs/heads/feature".into(),
9968 capabilities: Vec::new(),
9969 },
9970 ]
9971 );
9972 assert_eq!(
9973 parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9974 .expect("test operation should succeed"),
9975 set.refs
9976 );
9977 assert_eq!(
9978 encode_ref_advertisement_set(&set).expect("test operation should succeed"),
9979 frames
9980 );
9981 }
9982
9983 #[test]
9984 fn advertised_refs_streams_round_trip() {
9985 let advertisements = vec![RefAdvertisement {
9986 oid: ObjectId::from_hex(
9987 ObjectFormat::Sha1,
9988 "1111111111111111111111111111111111111111",
9989 )
9990 .expect("test operation should succeed"),
9991 name: "HEAD".into(),
9992 capabilities: vec![Capability {
9993 name: "symref".into(),
9994 value: Some("HEAD:refs/heads/main".into()),
9995 }],
9996 }];
9997 let mut encoded = Vec::new();
9998 write_ref_advertisements(&mut encoded, &advertisements)
9999 .expect("test operation should succeed");
10000 encoded.extend_from_slice(b"tail");
10001
10002 let mut input = encoded.as_slice();
10003 assert_eq!(
10004 read_ref_advertisements(ObjectFormat::Sha1, &mut input)
10005 .expect("test operation should succeed"),
10006 advertisements
10007 );
10008 assert_eq!(input, b"tail");
10009 }
10010
10011 #[test]
10012 fn advertised_ref_set_streams_round_trip() {
10013 let set = RefAdvertisementSet {
10014 protocol: ProtocolVersion::V1,
10015 refs: vec![RefAdvertisement {
10016 oid: ObjectId::from_hex(
10017 ObjectFormat::Sha1,
10018 "1111111111111111111111111111111111111111",
10019 )
10020 .expect("test operation should succeed"),
10021 name: "HEAD".into(),
10022 capabilities: vec![Capability {
10023 name: "symref".into(),
10024 value: Some("HEAD:refs/heads/main".into()),
10025 }],
10026 }],
10027 shallow: vec![
10028 ObjectId::from_hex(
10029 ObjectFormat::Sha1,
10030 "2222222222222222222222222222222222222222",
10031 )
10032 .expect("test operation should succeed"),
10033 ],
10034 };
10035 let mut encoded = Vec::new();
10036 write_ref_advertisement_set(&mut encoded, &set).expect("test operation should succeed");
10037 encoded.extend_from_slice(b"tail");
10038
10039 let mut input = encoded.as_slice();
10040 assert_eq!(
10041 read_ref_advertisement_set(ObjectFormat::Sha1, &mut input)
10042 .expect("test operation should succeed"),
10043 set
10044 );
10045 assert_eq!(input, b"tail");
10046 }
10047
10048 #[test]
10049 fn advertised_refs_reject_malformed_streams() {
10050 assert!(
10051 parse_ref_advertisements(
10052 ObjectFormat::Sha1,
10053 &[PktLineFrame::Data(
10054 b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),
10055 )],
10056 )
10057 .is_err()
10058 );
10059 assert!(
10060 parse_ref_advertisements(
10061 ObjectFormat::Sha1,
10062 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10063 )
10064 .is_err()
10065 );
10066 assert!(parse_ref_advertisements(
10067 ObjectFormat::Sha1,
10068 &[
10069 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
10070 PktLineFrame::Data(
10071 b"2222222222222222222222222222222222222222 refs/heads/main\0thin-pack\n"
10072 .to_vec(),
10073 ),
10074 PktLineFrame::Flush,
10075 ],
10076 )
10077 .is_err());
10078 assert!(parse_ref_advertisement_set(
10079 ObjectFormat::Sha1,
10080 &[
10081 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
10082 PktLineFrame::Data(b"version 1\n".to_vec()),
10083 PktLineFrame::Flush,
10084 ],
10085 )
10086 .is_err());
10087 assert!(
10088 parse_ref_advertisement_set(
10089 ObjectFormat::Sha1,
10090 &[
10091 PktLineFrame::Data(b"version 2\n".to_vec()),
10092 PktLineFrame::Flush,
10093 ],
10094 )
10095 .is_err()
10096 );
10097 assert!(
10098 parse_ref_advertisement_set(
10099 ObjectFormat::Sha1,
10100 &[
10101 PktLineFrame::Data(
10102 b"shallow 1111111111111111111111111111111111111111\n".to_vec()
10103 ),
10104 PktLineFrame::Flush,
10105 ],
10106 )
10107 .is_err()
10108 );
10109 assert!(parse_ref_advertisement_set(
10110 ObjectFormat::Sha1,
10111 &[
10112 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
10113 PktLineFrame::Data(b"shallow not-an-oid\n".to_vec()),
10114 PktLineFrame::Flush,
10115 ],
10116 )
10117 .is_err());
10118 assert!(parse_ref_advertisement_set(
10119 ObjectFormat::Sha1,
10120 &[
10121 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
10122 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
10123 PktLineFrame::Data(
10124 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
10125 ),
10126 PktLineFrame::Flush,
10127 ],
10128 )
10129 .is_err());
10130 assert!(
10131 encode_ref_advertisements(&[
10132 RefAdvertisement {
10133 oid: ObjectId::from_hex(
10134 ObjectFormat::Sha1,
10135 "1111111111111111111111111111111111111111",
10136 )
10137 .expect("test operation should succeed"),
10138 name: "HEAD".into(),
10139 capabilities: Vec::new(),
10140 },
10141 RefAdvertisement {
10142 oid: ObjectId::from_hex(
10143 ObjectFormat::Sha1,
10144 "2222222222222222222222222222222222222222",
10145 )
10146 .expect("test operation should succeed"),
10147 name: "refs/heads/main".into(),
10148 capabilities: vec![Capability {
10149 name: "thin-pack".into(),
10150 value: None,
10151 }],
10152 },
10153 ])
10154 .is_err()
10155 );
10156 assert!(
10157 encode_ref_advertisement(&RefAdvertisement {
10158 oid: ObjectId::from_hex(
10159 ObjectFormat::Sha1,
10160 "1111111111111111111111111111111111111111",
10161 )
10162 .expect("test operation should succeed"),
10163 name: "bad ref".into(),
10164 capabilities: Vec::new(),
10165 })
10166 .is_err()
10167 );
10168 assert!(
10169 encode_ref_advertisement_set(&RefAdvertisementSet {
10170 protocol: ProtocolVersion::V2,
10171 refs: Vec::new(),
10172 shallow: Vec::new(),
10173 })
10174 .is_err()
10175 );
10176 assert!(
10177 encode_ref_advertisement_set(&RefAdvertisementSet {
10178 protocol: ProtocolVersion::V0,
10179 refs: Vec::new(),
10180 shallow: vec![
10181 ObjectId::from_hex(
10182 ObjectFormat::Sha1,
10183 "1111111111111111111111111111111111111111",
10184 )
10185 .expect("test operation should succeed")
10186 ],
10187 })
10188 .is_err()
10189 );
10190 }
10191
10192 #[test]
10193 fn dumb_http_info_refs_parse_and_encode_records() {
10194 let main = ObjectId::from_hex(
10195 ObjectFormat::Sha1,
10196 "1111111111111111111111111111111111111111",
10197 )
10198 .expect("test operation should succeed");
10199 let tag = ObjectId::from_hex(
10200 ObjectFormat::Sha1,
10201 "2222222222222222222222222222222222222222",
10202 )
10203 .expect("test operation should succeed");
10204 let peeled = ObjectId::from_hex(
10205 ObjectFormat::Sha1,
10206 "3333333333333333333333333333333333333333",
10207 )
10208 .expect("test operation should succeed");
10209 let input = b"1111111111111111111111111111111111111111\trefs/heads/main\n2222222222222222222222222222222222222222\trefs/tags/v1.0\n3333333333333333333333333333333333333333\trefs/tags/v1.0^{}\n";
10210
10211 let records = parse_dumb_http_info_refs(ObjectFormat::Sha1, input)
10212 .expect("test operation should succeed");
10213 assert_eq!(
10214 records,
10215 vec![
10216 DumbHttpRefRecord {
10217 oid: main,
10218 name: "refs/heads/main".into(),
10219 peeled: false,
10220 },
10221 DumbHttpRefRecord {
10222 oid: tag,
10223 name: "refs/tags/v1.0".into(),
10224 peeled: false,
10225 },
10226 DumbHttpRefRecord {
10227 oid: peeled,
10228 name: "refs/tags/v1.0".into(),
10229 peeled: true,
10230 },
10231 ]
10232 );
10233 assert_eq!(
10234 encode_dumb_http_info_refs(&records).expect("test operation should succeed"),
10235 input
10236 );
10237 assert_eq!(
10238 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"")
10239 .expect("test operation should succeed"),
10240 Vec::<DumbHttpRefRecord>::new()
10241 );
10242 }
10243
10244 #[test]
10245 fn dumb_http_info_refs_streams_round_trip() {
10246 let records = vec![DumbHttpRefRecord {
10247 oid: ObjectId::from_hex(
10248 ObjectFormat::Sha1,
10249 "1111111111111111111111111111111111111111",
10250 )
10251 .expect("test operation should succeed"),
10252 name: "refs/heads/main".into(),
10253 peeled: false,
10254 }];
10255 let mut encoded = Vec::new();
10256 write_dumb_http_info_refs(&mut encoded, &records).expect("test operation should succeed");
10257 let mut input = encoded.as_slice();
10258 assert_eq!(
10259 read_dumb_http_info_refs(ObjectFormat::Sha1, &mut input)
10260 .expect("test operation should succeed"),
10261 records
10262 );
10263 assert!(input.is_empty());
10264 }
10265
10266 #[test]
10267 fn dumb_http_info_refs_reject_malformed_records() {
10268 assert!(
10269 parse_dumb_http_info_refs(
10270 ObjectFormat::Sha1,
10271 b"1111111111111111111111111111111111111111 refs/heads/main\n",
10272 )
10273 .is_err()
10274 );
10275 assert!(
10276 parse_dumb_http_info_refs(
10277 ObjectFormat::Sha1,
10278 b"1111111111111111111111111111111111111111\trefs/heads/main",
10279 )
10280 .is_err()
10281 );
10282 assert!(
10283 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"not-an-oid\trefs/heads/main\n")
10284 .is_err()
10285 );
10286 assert!(
10287 parse_dumb_http_info_refs(
10288 ObjectFormat::Sha1,
10289 b"1111111111111111111111111111111111111111\tbad ref\n",
10290 )
10291 .is_err()
10292 );
10293 assert!(
10294 encode_dumb_http_info_refs(&[DumbHttpRefRecord {
10295 oid: ObjectId::from_hex(
10296 ObjectFormat::Sha1,
10297 "1111111111111111111111111111111111111111",
10298 )
10299 .expect("test operation should succeed"),
10300 name: "refs/tags/v1.0^{}".into(),
10301 peeled: false,
10302 }])
10303 .is_err()
10304 );
10305 }
10306
10307 #[test]
10308 fn dumb_http_alternates_parse_and_encode_locations() {
10309 let input = b"https://example.com/base.git/objects/\n../other.git/objects/\n";
10310 let alternates = parse_dumb_http_alternates(input).expect("test operation should succeed");
10311 assert_eq!(
10312 alternates,
10313 vec![
10314 "https://example.com/base.git/objects/".to_string(),
10315 "../other.git/objects/".to_string(),
10316 ]
10317 );
10318 assert_eq!(
10319 encode_dumb_http_alternates(&alternates).expect("test operation should succeed"),
10320 input
10321 );
10322 assert_eq!(
10323 parse_dumb_http_alternates(b"").expect("test operation should succeed"),
10324 Vec::<String>::new()
10325 );
10326 }
10327
10328 #[test]
10329 fn dumb_http_alternates_streams_round_trip() {
10330 let alternates = vec!["https://example.com/base.git/objects/".to_string()];
10331 let mut encoded = Vec::new();
10332 write_dumb_http_alternates(&mut encoded, &alternates)
10333 .expect("test operation should succeed");
10334 let mut input = encoded.as_slice();
10335 assert_eq!(
10336 read_dumb_http_alternates(&mut input).expect("test operation should succeed"),
10337 alternates
10338 );
10339 assert!(input.is_empty());
10340 }
10341
10342 #[test]
10343 fn dumb_http_alternates_reject_malformed_lines() {
10344 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/").is_err());
10345 assert!(parse_dumb_http_alternates(b"\n").is_err());
10346 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/\r\n").is_err());
10347 assert!(encode_dumb_http_alternates(&["bad\nalternate".to_string()]).is_err());
10348 }
10349
10350 #[test]
10351 fn dumb_http_packs_parse_and_encode_pack_records() {
10352 let first = ObjectId::from_hex(
10353 ObjectFormat::Sha1,
10354 "1111111111111111111111111111111111111111",
10355 )
10356 .expect("test operation should succeed");
10357 let second = ObjectId::from_hex(
10358 ObjectFormat::Sha1,
10359 "2222222222222222222222222222222222222222",
10360 )
10361 .expect("test operation should succeed");
10362 let input = b"P pack-1111111111111111111111111111111111111111.pack\nP pack-2222222222222222222222222222222222222222.pack\n";
10363 let records = parse_dumb_http_packs(ObjectFormat::Sha1, input)
10364 .expect("test operation should succeed");
10365 assert_eq!(
10366 records,
10367 vec![
10368 DumbHttpPackRecord { hash: first },
10369 DumbHttpPackRecord { hash: second },
10370 ]
10371 );
10372 assert_eq!(
10373 encode_dumb_http_packs(&records).expect("test operation should succeed"),
10374 input
10375 );
10376 assert_eq!(
10377 parse_dumb_http_packs(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
10378 Vec::<DumbHttpPackRecord>::new()
10379 );
10380 }
10381
10382 #[test]
10383 fn dumb_http_packs_streams_round_trip() {
10384 let records = vec![DumbHttpPackRecord {
10385 hash: ObjectId::from_hex(
10386 ObjectFormat::Sha1,
10387 "1111111111111111111111111111111111111111",
10388 )
10389 .expect("test operation should succeed"),
10390 }];
10391 let mut encoded = Vec::new();
10392 write_dumb_http_packs(&mut encoded, &records).expect("test operation should succeed");
10393 let mut input = encoded.as_slice();
10394 assert_eq!(
10395 read_dumb_http_packs(ObjectFormat::Sha1, &mut input)
10396 .expect("test operation should succeed"),
10397 records
10398 );
10399 assert!(input.is_empty());
10400 }
10401
10402 #[test]
10403 fn dumb_http_packs_reject_malformed_records() {
10404 assert!(
10405 parse_dumb_http_packs(
10406 ObjectFormat::Sha1,
10407 b"P pack-1111111111111111111111111111111111111111.pack",
10408 )
10409 .is_err()
10410 );
10411 assert!(
10412 parse_dumb_http_packs(
10413 ObjectFormat::Sha1,
10414 b"pack-1111111111111111111111111111111111111111.pack\n",
10415 )
10416 .is_err()
10417 );
10418 assert!(parse_dumb_http_packs(ObjectFormat::Sha1, b"P pack-not-a-hash.pack\n",).is_err());
10419 assert!(
10420 parse_dumb_http_packs(
10421 ObjectFormat::Sha1,
10422 b"P pack-1111111111111111111111111111111111111111.idx\n",
10423 )
10424 .is_err()
10425 );
10426 }
10427
10428 #[test]
10429 fn upload_pack_features_parse_encode_and_validate_request() {
10430 let capabilities = vec![
10431 Capability {
10432 name: "multi_ack".into(),
10433 value: None,
10434 },
10435 Capability {
10436 name: "multi_ack_detailed".into(),
10437 value: None,
10438 },
10439 Capability {
10440 name: "no-done".into(),
10441 value: None,
10442 },
10443 Capability {
10444 name: "thin-pack".into(),
10445 value: None,
10446 },
10447 Capability {
10448 name: "side-band-64k".into(),
10449 value: None,
10450 },
10451 Capability {
10452 name: "ofs-delta".into(),
10453 value: None,
10454 },
10455 Capability {
10456 name: "shallow".into(),
10457 value: None,
10458 },
10459 Capability {
10460 name: "deepen-since".into(),
10461 value: None,
10462 },
10463 Capability {
10464 name: "deepen-not".into(),
10465 value: None,
10466 },
10467 Capability {
10468 name: "include-tag".into(),
10469 value: None,
10470 },
10471 Capability {
10472 name: "no-progress".into(),
10473 value: None,
10474 },
10475 Capability {
10476 name: "filter".into(),
10477 value: None,
10478 },
10479 Capability {
10480 name: "agent".into(),
10481 value: Some("git/2.54.0".into()),
10482 },
10483 Capability {
10484 name: "object-format".into(),
10485 value: Some("sha256".into()),
10486 },
10487 Capability {
10488 name: "symref".into(),
10489 value: Some("HEAD:refs/heads/main".into()),
10490 },
10491 Capability {
10492 name: "custom".into(),
10493 value: Some("value".into()),
10494 },
10495 ];
10496 let features =
10497 parse_upload_pack_features(&capabilities).expect("test operation should succeed");
10498 assert_eq!(
10499 features,
10500 UploadPackFeatures {
10501 multi_ack: true,
10502 multi_ack_detailed: true,
10503 no_done: true,
10504 thin_pack: true,
10505 side_band_64k: true,
10506 ofs_delta: true,
10507 shallow: true,
10508 deepen_since: true,
10509 deepen_not: true,
10510 include_tag: true,
10511 no_progress: true,
10512 filter: true,
10513 agent: Some("git/2.54.0".into()),
10514 object_format: Some(ObjectFormat::Sha256),
10515 symrefs: vec!["HEAD:refs/heads/main".into()],
10516 unknown: vec![Capability {
10517 name: "custom".into(),
10518 value: Some("value".into()),
10519 }],
10520 ..UploadPackFeatures::default()
10521 }
10522 );
10523 assert_eq!(
10524 encode_upload_pack_features(&features).expect("test operation should succeed"),
10525 capabilities
10526 );
10527
10528 let request = UploadPackRequest {
10529 wants: vec![
10530 ObjectId::from_hex(
10531 ObjectFormat::Sha1,
10532 "1111111111111111111111111111111111111111",
10533 )
10534 .expect("test operation should succeed"),
10535 ],
10536 capabilities: vec![
10537 Capability {
10538 name: "multi_ack_detailed".into(),
10539 value: None,
10540 },
10541 Capability {
10542 name: "thin-pack".into(),
10543 value: None,
10544 },
10545 Capability {
10546 name: "side-band-64k".into(),
10547 value: None,
10548 },
10549 Capability {
10550 name: "ofs-delta".into(),
10551 value: None,
10552 },
10553 Capability {
10554 name: "include-tag".into(),
10555 value: None,
10556 },
10557 Capability {
10558 name: "agent".into(),
10559 value: Some("sley".into()),
10560 },
10561 ],
10562 shallow: vec![
10563 ObjectId::from_hex(
10564 ObjectFormat::Sha1,
10565 "2222222222222222222222222222222222222222",
10566 )
10567 .expect("test operation should succeed"),
10568 ],
10569 deepen: Some(5),
10570 deepen_since: Some(1_710_000_000),
10571 deepen_not: vec!["refs/tags/base".into()],
10572 filter: Some("blob:none".into()),
10573 };
10574 validate_upload_pack_request_features(&features, &request)
10575 .expect("test operation should succeed");
10576 }
10577
10578 #[test]
10579 fn upload_pack_features_reject_invalid_requests() {
10580 let want = ObjectId::from_hex(
10581 ObjectFormat::Sha1,
10582 "1111111111111111111111111111111111111111",
10583 )
10584 .expect("test operation should succeed");
10585 let features = UploadPackFeatures {
10586 thin_pack: true,
10587 side_band: true,
10588 ..UploadPackFeatures::default()
10589 };
10590
10591 assert!(
10592 validate_upload_pack_request_features(
10593 &features,
10594 &UploadPackRequest {
10595 wants: vec![want],
10596 capabilities: vec![Capability {
10597 name: "ofs-delta".into(),
10598 value: None,
10599 }],
10600 ..UploadPackRequest::default()
10601 },
10602 )
10603 .is_err()
10604 );
10605 assert!(
10606 validate_upload_pack_request_features(
10607 &features,
10608 &UploadPackRequest {
10609 wants: vec![want],
10610 shallow: vec![want],
10611 ..UploadPackRequest::default()
10612 },
10613 )
10614 .is_err()
10615 );
10616 assert!(
10617 validate_upload_pack_request_features(
10618 &features,
10619 &UploadPackRequest {
10620 wants: vec![want],
10621 filter: Some("blob:none".into()),
10622 ..UploadPackRequest::default()
10623 },
10624 )
10625 .is_err()
10626 );
10627 assert!(
10628 validate_upload_pack_request_features(
10629 &UploadPackFeatures {
10630 side_band: true,
10631 side_band_64k: true,
10632 ..UploadPackFeatures::default()
10633 },
10634 &UploadPackRequest {
10635 wants: vec![want],
10636 capabilities: vec![
10637 Capability {
10638 name: "side-band".into(),
10639 value: None,
10640 },
10641 Capability {
10642 name: "side-band-64k".into(),
10643 value: None,
10644 },
10645 ],
10646 ..UploadPackRequest::default()
10647 },
10648 )
10649 .is_err()
10650 );
10651
10652 assert!(
10653 parse_upload_pack_features(&[
10654 Capability {
10655 name: "thin-pack".into(),
10656 value: None,
10657 },
10658 Capability {
10659 name: "thin-pack".into(),
10660 value: None,
10661 },
10662 ])
10663 .is_err()
10664 );
10665 assert!(
10666 encode_upload_pack_features(&UploadPackFeatures {
10667 unknown: vec![Capability {
10668 name: "filter".into(),
10669 value: None,
10670 }],
10671 ..UploadPackFeatures::default()
10672 })
10673 .is_err()
10674 );
10675 }
10676
10677 #[test]
10678 fn upload_pack_raw_response_builder_filters_unknown_haves_and_builds_pack() {
10679 let want = ObjectId::from_hex(
10680 ObjectFormat::Sha1,
10681 "1111111111111111111111111111111111111111",
10682 )
10683 .expect("test operation should succeed");
10684 let known_have = ObjectId::from_hex(
10685 ObjectFormat::Sha1,
10686 "2222222222222222222222222222222222222222",
10687 )
10688 .expect("test operation should succeed");
10689 let unknown_have = ObjectId::from_hex(
10690 ObjectFormat::Sha1,
10691 "3333333333333333333333333333333333333333",
10692 )
10693 .expect("test operation should succeed");
10694 let existing = std::collections::HashSet::from([want, known_have]);
10695
10696 let response = build_upload_pack_raw_packfile_response(
10697 &UploadPackFeatures::default(),
10698 UploadPackRequest {
10699 wants: vec![want],
10700 ..UploadPackRequest::default()
10701 },
10702 [known_have, unknown_have],
10703 |oid| Ok(existing.contains(oid)),
10704 |wants, haves| {
10705 assert_eq!(wants, vec![want]);
10706 assert_eq!(haves, vec![known_have]);
10707 Ok(Some(b"PACKmock".to_vec()))
10708 },
10709 )
10710 .expect("test operation should succeed");
10711
10712 assert_eq!(
10713 response.acknowledgments,
10714 vec![UploadPackAcknowledgment::Nak]
10715 );
10716 assert_eq!(response.packfile, b"PACKmock");
10717 }
10718
10719 #[test]
10720 fn upload_pack_raw_response_builder_rejects_missing_want_and_empty_pack() {
10721 let want = ObjectId::from_hex(
10722 ObjectFormat::Sha1,
10723 "1111111111111111111111111111111111111111",
10724 )
10725 .expect("test operation should succeed");
10726
10727 assert!(
10728 build_upload_pack_raw_packfile_response(
10729 &UploadPackFeatures::default(),
10730 UploadPackRequest {
10731 wants: vec![want],
10732 ..UploadPackRequest::default()
10733 },
10734 Vec::<ObjectId>::new(),
10735 |_| Ok(false),
10736 |_, _| Ok(Some(b"PACKmock".to_vec())),
10737 )
10738 .is_err()
10739 );
10740
10741 assert!(
10742 build_upload_pack_raw_packfile_response(
10743 &UploadPackFeatures::default(),
10744 UploadPackRequest {
10745 wants: vec![want],
10746 ..UploadPackRequest::default()
10747 },
10748 Vec::<ObjectId>::new(),
10749 |_| Ok(true),
10750 |_, _| Ok(None),
10751 )
10752 .is_err()
10753 );
10754 }
10755
10756 #[test]
10757 fn upload_pack_request_parses_and_encodes_initial_fetch_request() {
10758 let want = ObjectId::from_hex(
10759 ObjectFormat::Sha1,
10760 "1111111111111111111111111111111111111111",
10761 )
10762 .expect("test operation should succeed");
10763 let second_want = ObjectId::from_hex(
10764 ObjectFormat::Sha1,
10765 "2222222222222222222222222222222222222222",
10766 )
10767 .expect("test operation should succeed");
10768 let shallow = ObjectId::from_hex(
10769 ObjectFormat::Sha1,
10770 "3333333333333333333333333333333333333333",
10771 )
10772 .expect("test operation should succeed");
10773 let frames = vec![
10774 PktLineFrame::Data(
10775 b"want 1111111111111111111111111111111111111111 multi_ack thin-pack agent=git/2.54.0\n"
10776 .to_vec(),
10777 ),
10778 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10779 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
10780 PktLineFrame::Data(b"deepen-since 1710000000\n".to_vec()),
10781 PktLineFrame::Data(b"deepen-not refs/tags/base\n".to_vec()),
10782 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10783 PktLineFrame::Flush,
10784 ];
10785 let request = parse_upload_pack_request(ObjectFormat::Sha1, &frames)
10786 .expect("test operation should succeed")
10787 .expect("test operation should succeed");
10788 assert_eq!(
10789 request,
10790 UploadPackRequest {
10791 wants: vec![want, second_want],
10792 capabilities: vec![
10793 Capability {
10794 name: "multi_ack".into(),
10795 value: None,
10796 },
10797 Capability {
10798 name: "thin-pack".into(),
10799 value: None,
10800 },
10801 Capability {
10802 name: "agent".into(),
10803 value: Some("git/2.54.0".into()),
10804 },
10805 ],
10806 shallow: vec![shallow],
10807 deepen: None,
10808 deepen_since: Some(1_710_000_000),
10809 deepen_not: vec!["refs/tags/base".into()],
10810 filter: Some("blob:none".into()),
10811 }
10812 );
10813 assert_eq!(
10814 encode_upload_pack_request(Some(&request)).expect("test operation should succeed"),
10815 frames
10816 );
10817 assert_eq!(
10818 parse_upload_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10819 .expect("test operation should succeed"),
10820 None
10821 );
10822 assert_eq!(
10823 encode_upload_pack_request(None).expect("test operation should succeed"),
10824 vec![PktLineFrame::Flush]
10825 );
10826 }
10827
10828 #[test]
10829 fn upload_pack_request_streams_round_trip() {
10830 let request = UploadPackRequest {
10831 wants: vec![
10832 ObjectId::from_hex(
10833 ObjectFormat::Sha1,
10834 "1111111111111111111111111111111111111111",
10835 )
10836 .expect("test operation should succeed"),
10837 ],
10838 capabilities: vec![Capability {
10839 name: "ofs-delta".into(),
10840 value: None,
10841 }],
10842 deepen: Some(10),
10843 ..UploadPackRequest::default()
10844 };
10845 let mut encoded = Vec::new();
10846 write_upload_pack_request(&mut encoded, Some(&request))
10847 .expect("test operation should succeed");
10848 encoded.extend_from_slice(b"tail");
10849
10850 let mut input = encoded.as_slice();
10851 assert_eq!(
10852 read_upload_pack_request(ObjectFormat::Sha1, &mut input)
10853 .expect("test operation should succeed"),
10854 Some(request)
10855 );
10856 assert_eq!(input, b"tail");
10857 }
10858
10859 #[test]
10860 fn upload_pack_request_rejects_malformed_requests() {
10861 assert!(
10862 parse_upload_pack_request(
10863 ObjectFormat::Sha1,
10864 &[PktLineFrame::Data(
10865 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10866 )],
10867 )
10868 .is_err()
10869 );
10870 assert!(
10871 parse_upload_pack_request(
10872 ObjectFormat::Sha1,
10873 &[
10874 PktLineFrame::Data(
10875 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10876 ),
10877 PktLineFrame::Flush,
10878 ],
10879 )
10880 .is_err()
10881 );
10882 assert!(
10883 parse_upload_pack_request(
10884 ObjectFormat::Sha1,
10885 &[
10886 PktLineFrame::Data(
10887 b"want 1111111111111111111111111111111111111111 thin-pack\n".to_vec(),
10888 ),
10889 PktLineFrame::Data(
10890 b"want 2222222222222222222222222222222222222222 ofs-delta\n".to_vec(),
10891 ),
10892 PktLineFrame::Flush,
10893 ],
10894 )
10895 .is_err()
10896 );
10897 assert!(parse_upload_pack_request(
10898 ObjectFormat::Sha1,
10899 &[
10900 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10901 PktLineFrame::Data(b"deepen 1\n".to_vec()),
10902 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10903 PktLineFrame::Flush,
10904 ],
10905 )
10906 .is_err());
10907 assert!(parse_upload_pack_request(
10908 ObjectFormat::Sha1,
10909 &[
10910 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10911 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10912 PktLineFrame::Data(b"filter tree:0\n".to_vec()),
10913 PktLineFrame::Flush,
10914 ],
10915 )
10916 .is_err());
10917 assert!(encode_upload_pack_request(Some(&UploadPackRequest::default())).is_err());
10918 assert!(
10919 encode_upload_pack_request(Some(&UploadPackRequest {
10920 wants: vec![
10921 ObjectId::from_hex(
10922 ObjectFormat::Sha1,
10923 "1111111111111111111111111111111111111111",
10924 )
10925 .expect("test operation should succeed")
10926 ],
10927 deepen: Some(0),
10928 ..UploadPackRequest::default()
10929 }))
10930 .is_err()
10931 );
10932 }
10933
10934 #[test]
10935 fn upload_pack_shallow_update_parses_and_encodes_records() {
10936 let shallow = ObjectId::from_hex(
10937 ObjectFormat::Sha1,
10938 "1111111111111111111111111111111111111111",
10939 )
10940 .expect("test operation should succeed");
10941 let unshallow = ObjectId::from_hex(
10942 ObjectFormat::Sha1,
10943 "2222222222222222222222222222222222222222",
10944 )
10945 .expect("test operation should succeed");
10946 let frames = vec![
10947 PktLineFrame::Data(b"shallow 1111111111111111111111111111111111111111\n".to_vec()),
10948 PktLineFrame::Data(b"unshallow 2222222222222222222222222222222222222222\n".to_vec()),
10949 PktLineFrame::Flush,
10950 ];
10951 let entries = parse_upload_pack_shallow_update(ObjectFormat::Sha1, &frames)
10952 .expect("test operation should succeed");
10953 assert_eq!(
10954 entries,
10955 vec![
10956 ProtocolV2FetchShallowInfo::Shallow(shallow),
10957 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
10958 ]
10959 );
10960 assert_eq!(
10961 encode_upload_pack_shallow_update(&entries).expect("test operation should succeed"),
10962 frames
10963 );
10964 assert_eq!(
10965 parse_upload_pack_shallow_update(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10966 .expect("test operation should succeed"),
10967 Vec::<ProtocolV2FetchShallowInfo>::new()
10968 );
10969 }
10970
10971 #[test]
10972 fn upload_pack_shallow_update_streams_round_trip() {
10973 let entries = vec![ProtocolV2FetchShallowInfo::Shallow(
10974 ObjectId::from_hex(
10975 ObjectFormat::Sha1,
10976 "1111111111111111111111111111111111111111",
10977 )
10978 .expect("test operation should succeed"),
10979 )];
10980 let mut encoded = Vec::new();
10981 write_upload_pack_shallow_update(&mut encoded, &entries)
10982 .expect("test operation should succeed");
10983 encoded.extend_from_slice(b"tail");
10984
10985 let mut input = encoded.as_slice();
10986 assert_eq!(
10987 read_upload_pack_shallow_update(ObjectFormat::Sha1, &mut input)
10988 .expect("test operation should succeed"),
10989 entries
10990 );
10991 assert_eq!(input, b"tail");
10992 }
10993
10994 #[test]
10995 fn upload_pack_shallow_update_rejects_malformed_records() {
10996 assert!(
10997 parse_upload_pack_shallow_update(
10998 ObjectFormat::Sha1,
10999 &[PktLineFrame::Data(
11000 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
11001 )],
11002 )
11003 .is_err()
11004 );
11005 assert!(
11006 parse_upload_pack_shallow_update(
11007 ObjectFormat::Sha1,
11008 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
11009 )
11010 .is_err()
11011 );
11012 assert!(
11013 parse_upload_pack_shallow_update(
11014 ObjectFormat::Sha1,
11015 &[
11016 PktLineFrame::Data(
11017 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
11018 ),
11019 PktLineFrame::Flush,
11020 PktLineFrame::Data(
11021 b"unshallow 2222222222222222222222222222222222222222\n".to_vec(),
11022 ),
11023 ],
11024 )
11025 .is_err()
11026 );
11027 assert!(
11028 parse_upload_pack_shallow_update(
11029 ObjectFormat::Sha1,
11030 &[
11031 PktLineFrame::Data(
11032 b"unsupported 1111111111111111111111111111111111111111\n".to_vec(),
11033 ),
11034 PktLineFrame::Flush,
11035 ],
11036 )
11037 .is_err()
11038 );
11039 }
11040
11041 #[test]
11042 fn upload_pack_negotiation_request_parses_flush_and_done_rounds() {
11043 let have = ObjectId::from_hex(
11044 ObjectFormat::Sha1,
11045 "1111111111111111111111111111111111111111",
11046 )
11047 .expect("test operation should succeed");
11048 let second_have = ObjectId::from_hex(
11049 ObjectFormat::Sha1,
11050 "2222222222222222222222222222222222222222",
11051 )
11052 .expect("test operation should succeed");
11053 let flush_round = vec![
11054 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
11055 PktLineFrame::Data(b"have 2222222222222222222222222222222222222222\n".to_vec()),
11056 PktLineFrame::Flush,
11057 ];
11058 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &flush_round)
11059 .expect("test operation should succeed");
11060 assert_eq!(
11061 request,
11062 UploadPackNegotiationRequest {
11063 haves: vec![have, second_have],
11064 done: false,
11065 }
11066 );
11067 assert_eq!(
11068 encode_upload_pack_negotiation_request(&request)
11069 .expect("test operation should succeed"),
11070 flush_round
11071 );
11072
11073 let done_round = vec![
11074 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
11075 PktLineFrame::Data(b"done\n".to_vec()),
11076 ];
11077 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &done_round)
11078 .expect("test operation should succeed");
11079 assert_eq!(
11080 request,
11081 UploadPackNegotiationRequest {
11082 haves: vec![have],
11083 done: true,
11084 }
11085 );
11086 assert_eq!(
11087 encode_upload_pack_negotiation_request(&request)
11088 .expect("test operation should succeed"),
11089 done_round
11090 );
11091 }
11092
11093 #[test]
11094 fn upload_pack_negotiation_request_streams_round_trip() {
11095 let first = UploadPackNegotiationRequest {
11096 haves: vec![
11097 ObjectId::from_hex(
11098 ObjectFormat::Sha1,
11099 "1111111111111111111111111111111111111111",
11100 )
11101 .expect("test operation should succeed"),
11102 ],
11103 done: false,
11104 };
11105 let second = UploadPackNegotiationRequest {
11106 haves: Vec::new(),
11107 done: true,
11108 };
11109 let mut encoded = Vec::new();
11110 write_upload_pack_negotiation_request(&mut encoded, &first)
11111 .expect("test operation should succeed");
11112 write_upload_pack_negotiation_request(&mut encoded, &second)
11113 .expect("test operation should succeed");
11114 encoded.extend_from_slice(b"tail");
11115
11116 let mut input = encoded.as_slice();
11117 assert_eq!(
11118 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
11119 .expect("test operation should succeed"),
11120 first
11121 );
11122 assert_eq!(
11123 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
11124 .expect("test operation should succeed"),
11125 second
11126 );
11127 assert_eq!(input, b"tail");
11128 }
11129
11130 #[test]
11131 fn upload_pack_negotiation_request_rejects_malformed_rounds() {
11132 assert!(
11133 parse_upload_pack_negotiation_request(
11134 ObjectFormat::Sha1,
11135 &[PktLineFrame::Data(
11136 b"have 1111111111111111111111111111111111111111\n".to_vec(),
11137 )],
11138 )
11139 .is_err()
11140 );
11141 assert!(
11142 parse_upload_pack_negotiation_request(
11143 ObjectFormat::Sha1,
11144 &[PktLineFrame::Data(
11145 b"want 1111111111111111111111111111111111111111\n".to_vec(),
11146 )],
11147 )
11148 .is_err()
11149 );
11150 assert!(parse_upload_pack_negotiation_request(
11151 ObjectFormat::Sha1,
11152 &[
11153 PktLineFrame::Data(b"done\n".to_vec()),
11154 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec(),),
11155 ],
11156 )
11157 .is_err());
11158 assert!(
11159 parse_upload_pack_negotiation_request(
11160 ObjectFormat::Sha1,
11161 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
11162 )
11163 .is_err()
11164 );
11165 }
11166
11167 #[test]
11168 fn upload_pack_acknowledgments_parse_and_encode_statuses() {
11169 let oid = ObjectId::from_hex(
11170 ObjectFormat::Sha1,
11171 "1111111111111111111111111111111111111111",
11172 )
11173 .expect("test operation should succeed");
11174 assert_eq!(
11175 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"NAK\n")
11176 .expect("test operation should succeed"),
11177 UploadPackAcknowledgment::Nak
11178 );
11179 for (payload, status) in [
11180 (
11181 b"ACK 1111111111111111111111111111111111111111\n".as_slice(),
11182 None,
11183 ),
11184 (
11185 b"ACK 1111111111111111111111111111111111111111 continue\n".as_slice(),
11186 Some(UploadPackAckStatus::Continue),
11187 ),
11188 (
11189 b"ACK 1111111111111111111111111111111111111111 common\n".as_slice(),
11190 Some(UploadPackAckStatus::Common),
11191 ),
11192 (
11193 b"ACK 1111111111111111111111111111111111111111 ready\n".as_slice(),
11194 Some(UploadPackAckStatus::Ready),
11195 ),
11196 ] {
11197 let acknowledgment = parse_upload_pack_acknowledgment(ObjectFormat::Sha1, payload)
11198 .expect("test operation should succeed");
11199 assert_eq!(
11200 acknowledgment,
11201 UploadPackAcknowledgment::Ack { oid, status }
11202 );
11203 assert_eq!(
11204 encode_upload_pack_acknowledgment(&acknowledgment)
11205 .expect("test operation should succeed"),
11206 payload
11207 );
11208 }
11209 }
11210
11211 #[test]
11212 fn upload_pack_acknowledgments_stream_round_trip_and_reject_bad_lines() {
11213 let acknowledgment = UploadPackAcknowledgment::Ack {
11214 oid: ObjectId::from_hex(
11215 ObjectFormat::Sha1,
11216 "1111111111111111111111111111111111111111",
11217 )
11218 .expect("test operation should succeed"),
11219 status: Some(UploadPackAckStatus::Ready),
11220 };
11221 let mut encoded = Vec::new();
11222 write_upload_pack_acknowledgment(&mut encoded, &acknowledgment)
11223 .expect("test operation should succeed");
11224 encoded.extend_from_slice(b"tail");
11225
11226 let mut input = encoded.as_slice();
11227 assert_eq!(
11228 read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut input)
11229 .expect("test operation should succeed"),
11230 acknowledgment
11231 );
11232 assert_eq!(input, b"tail");
11233 assert!(parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ACK not-an-oid\n").is_err());
11234 assert!(
11235 parse_upload_pack_acknowledgment(
11236 ObjectFormat::Sha1,
11237 b"ACK 1111111111111111111111111111111111111111 unknown\n",
11238 )
11239 .is_err()
11240 );
11241 assert!(
11242 parse_upload_pack_acknowledgment(
11243 ObjectFormat::Sha1,
11244 b"ACK 1111111111111111111111111111111111111111 ready extra\n",
11245 )
11246 .is_err()
11247 );
11248 assert!(
11249 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ERR remote died\n").is_err()
11250 );
11251 assert!(read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut &b"0000"[..]).is_err());
11252 }
11253
11254 #[test]
11255 fn upload_pack_packfile_response_parses_acknowledgments_and_sideband() {
11256 let oid = ObjectId::from_hex(
11257 ObjectFormat::Sha1,
11258 "1111111111111111111111111111111111111111",
11259 )
11260 .expect("test operation should succeed");
11261 let frames = vec![
11262 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()),
11263 PktLineFrame::Data(b"NAK\n".to_vec()),
11264 PktLineFrame::Data(b"\x01PACK".to_vec()),
11265 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
11266 PktLineFrame::Data(b"\x01 bytes".to_vec()),
11267 PktLineFrame::Flush,
11268 ];
11269 let response = parse_upload_pack_packfile_response(ObjectFormat::Sha1, &frames)
11270 .expect("test operation should succeed");
11271 assert_eq!(
11272 response,
11273 UploadPackPackfileResponse {
11274 acknowledgments: vec![
11275 UploadPackAcknowledgment::Ack {
11276 oid,
11277 status: Some(UploadPackAckStatus::Common),
11278 },
11279 UploadPackAcknowledgment::Nak,
11280 ],
11281 sideband: vec![
11282 SideBandPacket {
11283 channel: SideBandChannel::Data,
11284 data: b"PACK".to_vec(),
11285 },
11286 SideBandPacket {
11287 channel: SideBandChannel::Progress,
11288 data: b"counting objects\n".to_vec(),
11289 },
11290 SideBandPacket {
11291 channel: SideBandChannel::Data,
11292 data: b" bytes".to_vec(),
11293 },
11294 ],
11295 }
11296 );
11297 assert_eq!(
11298 demux_upload_pack_packfile_response(&response).expect("test operation should succeed"),
11299 SideBandDemux {
11300 data: b"PACK bytes".to_vec(),
11301 progress: vec![b"counting objects\n".to_vec()],
11302 }
11303 );
11304 assert_eq!(
11305 encode_upload_pack_packfile_response(&response).expect("test operation should succeed"),
11306 frames
11307 );
11308 }
11309
11310 #[test]
11311 fn upload_pack_packfile_response_streams_round_trip() {
11312 let response = UploadPackPackfileResponse {
11313 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11314 sideband: vec![SideBandPacket {
11315 channel: SideBandChannel::Data,
11316 data: b"PACK".to_vec(),
11317 }],
11318 };
11319 let mut encoded = Vec::new();
11320 write_upload_pack_packfile_response(&mut encoded, &response)
11321 .expect("test operation should succeed");
11322 encoded.extend_from_slice(b"tail");
11323
11324 let mut input = encoded.as_slice();
11325 assert_eq!(
11326 read_upload_pack_packfile_response(ObjectFormat::Sha1, &mut input)
11327 .expect("test operation should succeed"),
11328 response
11329 );
11330 assert_eq!(input, b"tail");
11331 }
11332
11333 #[test]
11334 fn upload_pack_packfile_response_rejects_malformed_streams() {
11335 assert!(
11336 parse_upload_pack_packfile_response(
11337 ObjectFormat::Sha1,
11338 &[PktLineFrame::Data(b"NAK\n".to_vec())],
11339 )
11340 .is_err()
11341 );
11342 assert!(
11343 parse_upload_pack_packfile_response(
11344 ObjectFormat::Sha1,
11345 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
11346 )
11347 .is_err()
11348 );
11349 assert!(
11350 parse_upload_pack_packfile_response(
11351 ObjectFormat::Sha1,
11352 &[
11353 PktLineFrame::Data(b"\x01PACK".to_vec()),
11354 PktLineFrame::Data(
11355 b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()
11356 ),
11357 PktLineFrame::Flush,
11358 ],
11359 )
11360 .is_err()
11361 );
11362 assert!(
11363 parse_upload_pack_packfile_response(
11364 ObjectFormat::Sha1,
11365 &[
11366 PktLineFrame::Data(b"NAK\n".to_vec()),
11367 PktLineFrame::Flush,
11368 PktLineFrame::Data(b"\x01PACK".to_vec()),
11369 ],
11370 )
11371 .is_err()
11372 );
11373 assert!(
11374 parse_upload_pack_packfile_response(
11375 ObjectFormat::Sha1,
11376 &[
11377 PktLineFrame::Data(b"NAK\n".to_vec()),
11378 PktLineFrame::Data(b"\x04bad".to_vec()),
11379 PktLineFrame::Flush,
11380 ],
11381 )
11382 .is_err()
11383 );
11384 }
11385
11386 #[test]
11387 fn upload_pack_raw_packfile_response_parses_acknowledgments_and_raw_pack() {
11388 let oid = ObjectId::from_hex(
11389 ObjectFormat::Sha1,
11390 "1111111111111111111111111111111111111111",
11391 )
11392 .expect("test operation should succeed");
11393 let response = UploadPackRawPackfileResponse {
11394 acknowledgments: vec![
11395 UploadPackAcknowledgment::Ack {
11396 oid,
11397 status: Some(UploadPackAckStatus::Common),
11398 },
11399 UploadPackAcknowledgment::Nak,
11400 ],
11401 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11402 };
11403 let encoded = encode_upload_pack_raw_packfile_response(&response)
11404 .expect("test operation should succeed");
11405 assert_eq!(
11406 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &encoded)
11407 .expect("test operation should succeed"),
11408 response
11409 );
11410 }
11411
11412 #[test]
11413 fn upload_pack_raw_packfile_response_streams_round_trip() {
11414 let response = UploadPackRawPackfileResponse {
11415 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11416 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11417 };
11418 let mut encoded = Vec::new();
11419 write_upload_pack_raw_packfile_response(&mut encoded, &response)
11420 .expect("test operation should succeed");
11421 assert_eq!(
11422 encoded,
11423 encode_upload_pack_raw_packfile_response(&response)
11424 .expect("test operation should succeed")
11425 );
11426
11427 let mut input = encoded.as_slice();
11428 assert_eq!(
11429 read_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &mut input)
11430 .expect("test operation should succeed"),
11431 response
11432 );
11433 assert!(input.is_empty());
11434 }
11435
11436 #[test]
11437 fn upload_pack_raw_packfile_response_rejects_malformed_streams() {
11438 let ack = PktLineFrame::data(b"NAK\n".to_vec())
11439 .expect("test operation should succeed")
11440 .try_encode()
11441 .expect("test operation should succeed");
11442 let bad_ack = PktLineFrame::data(b"ACK not-an-oid\n".to_vec())
11443 .expect("test operation should succeed")
11444 .try_encode()
11445 .expect("test operation should succeed");
11446 let non_ack =
11447 PktLineFrame::data(b"have 1111111111111111111111111111111111111111\n".to_vec())
11448 .expect("test operation should succeed")
11449 .try_encode()
11450 .expect("test operation should succeed");
11451 let mut garbage_after_ack = ack.clone();
11452 garbage_after_ack.extend_from_slice(b"garbage");
11453
11454 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"").is_err());
11455 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &ack).is_err());
11456 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &bad_ack).is_err());
11457 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"0000PACK").is_err());
11458 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &non_ack).is_err());
11459 assert!(
11460 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &garbage_after_ack)
11461 .is_err()
11462 );
11463 assert!(
11464 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11465 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11466 packfile: Vec::new(),
11467 })
11468 .is_err()
11469 );
11470 assert!(
11471 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11472 acknowledgments: Vec::new(),
11473 packfile: b"not-a-pack".to_vec(),
11474 })
11475 .is_err()
11476 );
11477 }
11478
11479 #[test]
11480 fn upload_pack_request_encodes_deepen_request() {
11481 let want = ObjectId::from_hex(
11486 ObjectFormat::Sha1,
11487 "1111111111111111111111111111111111111111",
11488 )
11489 .expect("test operation should succeed");
11490 let boundary = ObjectId::from_hex(
11491 ObjectFormat::Sha1,
11492 "2222222222222222222222222222222222222222",
11493 )
11494 .expect("test operation should succeed");
11495 let request = UploadPackRequest {
11496 wants: vec![want],
11497 capabilities: vec![Capability {
11498 name: "shallow".into(),
11499 value: None,
11500 }],
11501 shallow: vec![boundary],
11502 deepen: Some(1),
11503 ..UploadPackRequest::default()
11504 };
11505 let mut encoded = Vec::new();
11506 write_upload_pack_request(&mut encoded, Some(&request))
11507 .expect("test operation should succeed");
11508 let mut expected = Vec::new();
11509 expected.extend_from_slice(b"003awant 1111111111111111111111111111111111111111 shallow\n");
11510 expected.extend_from_slice(b"0035shallow 2222222222222222222222222222222222222222\n");
11511 expected.extend_from_slice(b"000ddeepen 1\n");
11512 expected.extend_from_slice(b"0000");
11513 assert_eq!(encoded, expected);
11514 }
11515
11516 #[test]
11517 fn upload_pack_shallow_info_response_parses_shallow_unshallow_and_pack() {
11518 let shallow = ObjectId::from_hex(
11522 ObjectFormat::Sha1,
11523 "1111111111111111111111111111111111111111",
11524 )
11525 .expect("test operation should succeed");
11526 let unshallow = ObjectId::from_hex(
11527 ObjectFormat::Sha1,
11528 "2222222222222222222222222222222222222222",
11529 )
11530 .expect("test operation should succeed");
11531 let mut input = Vec::new();
11532 input.extend_from_slice(b"0035shallow 1111111111111111111111111111111111111111\n");
11533 input.extend_from_slice(b"0037unshallow 2222222222222222222222222222222222222222\n");
11534 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11536 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11537
11538 let (entries, response) =
11539 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11540 .expect("test operation should succeed");
11541 assert_eq!(
11542 entries,
11543 vec![
11544 ProtocolV2FetchShallowInfo::Shallow(shallow),
11545 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
11546 ]
11547 );
11548 assert_eq!(
11549 response,
11550 UploadPackRawPackfileResponse {
11551 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11552 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11553 }
11554 );
11555
11556 let mut stream = input.as_slice();
11558 let (read_entries, read_response) =
11559 read_upload_pack_shallow_info_and_raw_packfile_response(
11560 ObjectFormat::Sha1,
11561 &mut stream,
11562 )
11563 .expect("test operation should succeed");
11564 assert_eq!(read_entries, entries);
11565 assert_eq!(read_response, response);
11566 }
11567
11568 #[test]
11569 fn upload_pack_shallow_info_response_handles_empty_shallow_section() {
11570 let mut input = Vec::new();
11573 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11575 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11576
11577 let (entries, response) =
11578 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11579 .expect("test operation should succeed");
11580 assert!(entries.is_empty());
11581 assert_eq!(
11582 response.acknowledgments,
11583 vec![UploadPackAcknowledgment::Nak]
11584 );
11585 assert!(response.packfile.starts_with(b"PACK"));
11586 }
11587
11588 #[test]
11589 fn upload_pack_shallow_info_response_rejects_malformed_sections() {
11590 let truncated = b"0035shallow 1111111111111111111111111111111111111111\n".to_vec();
11592 assert!(
11593 parse_upload_pack_shallow_info_and_raw_packfile_response(
11594 ObjectFormat::Sha1,
11595 &truncated
11596 )
11597 .is_err()
11598 );
11599 let mut delimiter_section = Vec::new();
11601 delimiter_section.extend_from_slice(b"0001"); assert!(
11603 parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &delimiter_section).is_err()
11604 );
11605 let mut bad_line = Vec::new();
11607 bad_line.extend_from_slice(b"0008NAK\n");
11608 assert!(parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &bad_line).is_err());
11609 let mut no_pack = Vec::new();
11611 no_pack.extend_from_slice(b"0000"); no_pack.extend_from_slice(b"0008NAK\n");
11613 assert!(
11614 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &no_pack)
11615 .is_err()
11616 );
11617 }
11618
11619 #[test]
11620 fn receive_pack_request_parses_and_encodes_commands() {
11621 let old_id = ObjectId::from_hex(
11622 ObjectFormat::Sha1,
11623 "1111111111111111111111111111111111111111",
11624 )
11625 .expect("test operation should succeed");
11626 let new_id = ObjectId::from_hex(
11627 ObjectFormat::Sha1,
11628 "2222222222222222222222222222222222222222",
11629 )
11630 .expect("test operation should succeed");
11631 let delete_old_id = ObjectId::from_hex(
11632 ObjectFormat::Sha1,
11633 "3333333333333333333333333333333333333333",
11634 )
11635 .expect("test operation should succeed");
11636 let zero = ObjectId::from_hex(
11637 ObjectFormat::Sha1,
11638 "0000000000000000000000000000000000000000",
11639 )
11640 .expect("test operation should succeed");
11641 let shallow = ObjectId::from_hex(
11642 ObjectFormat::Sha1,
11643 "4444444444444444444444444444444444444444",
11644 )
11645 .expect("test operation should succeed");
11646 let frames = vec![
11647 PktLineFrame::Data(b"shallow 4444444444444444444444444444444444444444\n".to_vec()),
11648 PktLineFrame::Data(
11649 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status side-band-64k agent=git/2.54.0\n"
11650 .to_vec(),
11651 ),
11652 PktLineFrame::Data(
11653 b"3333333333333333333333333333333333333333 0000000000000000000000000000000000000000 refs/heads/old\n"
11654 .to_vec(),
11655 ),
11656 PktLineFrame::Flush,
11657 ];
11658 let request = parse_receive_pack_request(ObjectFormat::Sha1, &frames)
11659 .expect("test operation should succeed");
11660 assert_eq!(
11661 request,
11662 ReceivePackRequest {
11663 shallow: vec![shallow],
11664 commands: vec![
11665 ReceivePackCommand {
11666 old_id,
11667 new_id,
11668 name: "refs/heads/main".into(),
11669 },
11670 ReceivePackCommand {
11671 old_id: delete_old_id,
11672 new_id: zero,
11673 name: "refs/heads/old".into(),
11674 },
11675 ],
11676 capabilities: vec![
11677 Capability {
11678 name: "report-status".into(),
11679 value: None,
11680 },
11681 Capability {
11682 name: "side-band-64k".into(),
11683 value: None,
11684 },
11685 Capability {
11686 name: "agent".into(),
11687 value: Some("git/2.54.0".into()),
11688 },
11689 ],
11690 }
11691 );
11692 assert_eq!(
11693 encode_receive_pack_request(&request).expect("test operation should succeed"),
11694 frames
11695 );
11696 assert_eq!(
11697 parse_receive_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
11698 .expect("test operation should succeed"),
11699 ReceivePackRequest::default()
11700 );
11701 }
11702
11703 #[test]
11704 fn receive_pack_request_streams_round_trip() {
11705 let request = ReceivePackRequest {
11706 commands: vec![ReceivePackCommand {
11707 old_id: ObjectId::from_hex(
11708 ObjectFormat::Sha1,
11709 "0000000000000000000000000000000000000000",
11710 )
11711 .expect("test operation should succeed"),
11712 new_id: ObjectId::from_hex(
11713 ObjectFormat::Sha1,
11714 "1111111111111111111111111111111111111111",
11715 )
11716 .expect("test operation should succeed"),
11717 name: "refs/heads/main".into(),
11718 }],
11719 capabilities: vec![Capability {
11720 name: "report-status".into(),
11721 value: None,
11722 }],
11723 ..ReceivePackRequest::default()
11724 };
11725 let mut encoded = Vec::new();
11726 write_receive_pack_request(&mut encoded, &request).expect("test operation should succeed");
11727 encoded.extend_from_slice(b"PACK");
11728
11729 let mut input = encoded.as_slice();
11730 assert_eq!(
11731 read_receive_pack_request(ObjectFormat::Sha1, &mut input)
11732 .expect("test operation should succeed"),
11733 request
11734 );
11735 assert_eq!(input, b"PACK");
11736 }
11737
11738 #[test]
11739 fn receive_pack_request_rejects_malformed_commands() {
11740 assert!(
11741 parse_receive_pack_request(
11742 ObjectFormat::Sha1,
11743 &[PktLineFrame::Data(
11744 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11745 .to_vec(),
11746 )],
11747 )
11748 .is_err()
11749 );
11750 assert!(
11751 parse_receive_pack_request(
11752 ObjectFormat::Sha1,
11753 &[
11754 PktLineFrame::Data(
11755 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11756 .to_vec(),
11757 ),
11758 PktLineFrame::Data(
11759 b"shallow 3333333333333333333333333333333333333333\n".to_vec(),
11760 ),
11761 PktLineFrame::Flush,
11762 ],
11763 )
11764 .is_err()
11765 );
11766 assert!(
11767 parse_receive_pack_request(
11768 ObjectFormat::Sha1,
11769 &[
11770 PktLineFrame::Data(
11771 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status\n"
11772 .to_vec(),
11773 ),
11774 PktLineFrame::Data(
11775 b"3333333333333333333333333333333333333333 4444444444444444444444444444444444444444 refs/heads/next\0side-band-64k\n"
11776 .to_vec(),
11777 ),
11778 PktLineFrame::Flush,
11779 ],
11780 )
11781 .is_err()
11782 );
11783 assert!(
11784 parse_receive_pack_request(
11785 ObjectFormat::Sha1,
11786 &[
11787 PktLineFrame::Data(
11788 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
11789 ),
11790 PktLineFrame::Flush,
11791 ],
11792 )
11793 .is_err()
11794 );
11795 assert!(
11796 encode_receive_pack_request(&ReceivePackRequest {
11797 shallow: vec![
11798 ObjectId::from_hex(
11799 ObjectFormat::Sha1,
11800 "1111111111111111111111111111111111111111",
11801 )
11802 .expect("test operation should succeed")
11803 ],
11804 ..ReceivePackRequest::default()
11805 })
11806 .is_err()
11807 );
11808 assert!(
11809 encode_receive_pack_request(&ReceivePackRequest {
11810 commands: vec![ReceivePackCommand {
11811 old_id: ObjectId::from_hex(
11812 ObjectFormat::Sha1,
11813 "1111111111111111111111111111111111111111",
11814 )
11815 .expect("test operation should succeed"),
11816 new_id: ObjectId::from_hex(
11817 ObjectFormat::Sha1,
11818 "2222222222222222222222222222222222222222",
11819 )
11820 .expect("test operation should succeed"),
11821 name: "bad ref".into(),
11822 }],
11823 ..ReceivePackRequest::default()
11824 })
11825 .is_err()
11826 );
11827 }
11828
11829 #[test]
11830 fn receive_pack_features_parse_encode_and_validate_push_request() {
11831 let capabilities = vec![
11832 Capability {
11833 name: "report-status".into(),
11834 value: None,
11835 },
11836 Capability {
11837 name: "report-status-v2".into(),
11838 value: None,
11839 },
11840 Capability {
11841 name: "delete-refs".into(),
11842 value: None,
11843 },
11844 Capability {
11845 name: "ofs-delta".into(),
11846 value: None,
11847 },
11848 Capability {
11849 name: "atomic".into(),
11850 value: None,
11851 },
11852 Capability {
11853 name: "push-options".into(),
11854 value: None,
11855 },
11856 Capability {
11857 name: "side-band-64k".into(),
11858 value: None,
11859 },
11860 Capability {
11861 name: "quiet".into(),
11862 value: None,
11863 },
11864 Capability {
11865 name: "no-thin".into(),
11866 value: None,
11867 },
11868 Capability {
11869 name: "agent".into(),
11870 value: Some("git/2.54.0".into()),
11871 },
11872 Capability {
11873 name: "object-format".into(),
11874 value: Some("sha256".into()),
11875 },
11876 Capability {
11877 name: "custom".into(),
11878 value: Some("value".into()),
11879 },
11880 ];
11881 let features =
11882 parse_receive_pack_features(&capabilities).expect("test operation should succeed");
11883 assert_eq!(
11884 features,
11885 ReceivePackFeatures {
11886 report_status: true,
11887 report_status_v2: true,
11888 delete_refs: true,
11889 ofs_delta: true,
11890 atomic: true,
11891 push_options: true,
11892 side_band_64k: true,
11893 quiet: true,
11894 no_thin: true,
11895 agent: Some("git/2.54.0".into()),
11896 object_format: Some(ObjectFormat::Sha256),
11897 unknown: vec![Capability {
11898 name: "custom".into(),
11899 value: Some("value".into()),
11900 }],
11901 }
11902 );
11903 assert_eq!(
11904 encode_receive_pack_features(&features).expect("test operation should succeed"),
11905 capabilities
11906 );
11907
11908 let request = ReceivePackPushRequest {
11909 commands: ReceivePackRequest {
11910 commands: vec![ReceivePackCommand {
11911 old_id: ObjectId::from_hex(
11912 ObjectFormat::Sha1,
11913 "1111111111111111111111111111111111111111",
11914 )
11915 .expect("test operation should succeed"),
11916 new_id: ObjectId::from_hex(
11917 ObjectFormat::Sha1,
11918 "2222222222222222222222222222222222222222",
11919 )
11920 .expect("test operation should succeed"),
11921 name: "refs/heads/main".into(),
11922 }],
11923 capabilities: vec![
11924 Capability {
11925 name: "report-status".into(),
11926 value: None,
11927 },
11928 Capability {
11929 name: "ofs-delta".into(),
11930 value: None,
11931 },
11932 Capability {
11933 name: "push-options".into(),
11934 value: None,
11935 },
11936 Capability {
11937 name: "side-band-64k".into(),
11938 value: None,
11939 },
11940 Capability {
11941 name: "agent".into(),
11942 value: Some("sley".into()),
11943 },
11944 ],
11945 ..ReceivePackRequest::default()
11946 },
11947 push_options: Some(vec!["ci.skip".into()]),
11948 packfile: b"PACKpayload".to_vec(),
11949 };
11950 validate_receive_pack_push_request_features(&features, &request)
11951 .expect("test operation should succeed");
11952 }
11953
11954 #[test]
11955 fn receive_pack_features_reject_invalid_push_requests() {
11956 let old_id = ObjectId::from_hex(
11957 ObjectFormat::Sha1,
11958 "1111111111111111111111111111111111111111",
11959 )
11960 .expect("test operation should succeed");
11961 let new_id = ObjectId::from_hex(
11962 ObjectFormat::Sha1,
11963 "2222222222222222222222222222222222222222",
11964 )
11965 .expect("test operation should succeed");
11966 let zero = ObjectId::from_hex(
11967 ObjectFormat::Sha1,
11968 "0000000000000000000000000000000000000000",
11969 )
11970 .expect("test operation should succeed");
11971 let features = ReceivePackFeatures {
11972 report_status: true,
11973 push_options: true,
11974 ..ReceivePackFeatures::default()
11975 };
11976 let update = ReceivePackCommand {
11977 old_id: old_id.clone(),
11978 new_id: new_id.clone(),
11979 name: "refs/heads/main".into(),
11980 };
11981
11982 assert!(
11983 validate_receive_pack_push_request_features(
11984 &features,
11985 &ReceivePackPushRequest {
11986 commands: ReceivePackRequest {
11987 commands: vec![update.clone()],
11988 capabilities: vec![Capability {
11989 name: "push-options".into(),
11990 value: None,
11991 }],
11992 ..ReceivePackRequest::default()
11993 },
11994 push_options: None,
11995 packfile: b"PACKpayload".to_vec(),
11996 },
11997 )
11998 .is_err()
11999 );
12000 assert!(
12001 validate_receive_pack_push_request_features(
12002 &features,
12003 &ReceivePackPushRequest {
12004 commands: ReceivePackRequest {
12005 commands: vec![update.clone()],
12006 ..ReceivePackRequest::default()
12007 },
12008 push_options: Some(Vec::new()),
12009 packfile: b"PACKpayload".to_vec(),
12010 },
12011 )
12012 .is_err()
12013 );
12014 assert!(
12015 validate_receive_pack_push_request_features(
12016 &features,
12017 &ReceivePackPushRequest {
12018 commands: ReceivePackRequest {
12019 commands: vec![ReceivePackCommand {
12020 old_id: old_id.clone(),
12021 new_id: zero.clone(),
12022 name: "refs/heads/main".into(),
12023 }],
12024 ..ReceivePackRequest::default()
12025 },
12026 push_options: None,
12027 packfile: Vec::new(),
12028 },
12029 )
12030 .is_err()
12031 );
12032 validate_receive_pack_push_request_features(
12033 &features,
12034 &ReceivePackPushRequest {
12035 commands: ReceivePackRequest {
12036 commands: vec![update.clone()],
12037 ..ReceivePackRequest::default()
12038 },
12039 push_options: None,
12040 packfile: Vec::new(),
12041 },
12042 )
12043 .expect("updates to already-present objects may omit a packfile");
12044 assert!(
12045 validate_receive_pack_push_request_features(
12046 &ReceivePackFeatures {
12047 delete_refs: true,
12048 ..ReceivePackFeatures::default()
12049 },
12050 &ReceivePackPushRequest {
12051 commands: ReceivePackRequest {
12052 commands: vec![ReceivePackCommand {
12053 old_id,
12054 new_id: zero,
12055 name: "refs/heads/main".into(),
12056 }],
12057 ..ReceivePackRequest::default()
12058 },
12059 push_options: None,
12060 packfile: b"PACKpayload".to_vec(),
12061 },
12062 )
12063 .is_err()
12064 );
12065 assert!(
12066 validate_receive_pack_push_request_features(
12067 &features,
12068 &ReceivePackPushRequest {
12069 commands: ReceivePackRequest {
12070 commands: vec![update],
12071 capabilities: vec![Capability {
12072 name: "atomic".into(),
12073 value: None,
12074 }],
12075 ..ReceivePackRequest::default()
12076 },
12077 push_options: None,
12078 packfile: b"PACKpayload".to_vec(),
12079 },
12080 )
12081 .is_err()
12082 );
12083
12084 assert!(
12085 parse_receive_pack_features(&[
12086 Capability {
12087 name: "push-options".into(),
12088 value: None,
12089 },
12090 Capability {
12091 name: "push-options".into(),
12092 value: None,
12093 },
12094 ])
12095 .is_err()
12096 );
12097 assert!(
12098 encode_receive_pack_features(&ReceivePackFeatures {
12099 unknown: vec![Capability {
12100 name: "atomic".into(),
12101 value: None,
12102 }],
12103 ..ReceivePackFeatures::default()
12104 })
12105 .is_err()
12106 );
12107 }
12108
12109 #[test]
12110 fn receive_pack_apply_helper_installs_pack_verifies_objects_and_reports_ok() {
12111 let old_id = ObjectId::from_hex(
12112 ObjectFormat::Sha1,
12113 "1111111111111111111111111111111111111111",
12114 )
12115 .expect("test operation should succeed");
12116 let new_id = ObjectId::from_hex(
12117 ObjectFormat::Sha1,
12118 "2222222222222222222222222222222222222222",
12119 )
12120 .expect("test operation should succeed");
12121 let request = ReceivePackPushRequest {
12122 commands: ReceivePackRequest {
12123 commands: vec![ReceivePackCommand {
12124 old_id: old_id.clone(),
12125 new_id: new_id.clone(),
12126 name: "refs/heads/main".into(),
12127 }],
12128 ..ReceivePackRequest::default()
12129 },
12130 packfile: b"PACKpayload".to_vec(),
12131 ..ReceivePackPushRequest::default()
12132 };
12133 let installed = std::cell::Cell::new(false);
12134 let applied = std::cell::RefCell::new(Vec::new());
12135
12136 let report = apply_receive_pack_push_request(
12137 &ReceivePackFeatures::default(),
12138 &request,
12139 |_| unreachable!("update stale-old checks belong to the ref transaction callback"),
12140 |packfile| {
12141 assert_eq!(packfile, b"PACKpayload");
12142 installed.set(true);
12143 Ok(())
12144 },
12145 |oid| Ok(oid == &new_id),
12146 |commands| {
12147 applied.borrow_mut().extend_from_slice(commands);
12148 Ok(())
12149 },
12150 |_| unreachable!("no delete command should be applied"),
12151 )
12152 .expect("test operation should succeed");
12153
12154 assert!(installed.get());
12155 assert_eq!(applied.into_inner(), request.commands.commands);
12156 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
12157 assert_eq!(
12158 report.commands,
12159 vec![ReceivePackCommandStatus::Ok {
12160 name: "refs/heads/main".into(),
12161 }]
12162 );
12163 }
12164
12165 #[test]
12166 fn receive_pack_apply_helper_allows_update_without_pack_when_object_exists() {
12167 let old_id = ObjectId::from_hex(
12168 ObjectFormat::Sha1,
12169 "1111111111111111111111111111111111111111",
12170 )
12171 .expect("test operation should succeed");
12172 let new_id = ObjectId::from_hex(
12173 ObjectFormat::Sha1,
12174 "2222222222222222222222222222222222222222",
12175 )
12176 .expect("test operation should succeed");
12177 let request = ReceivePackPushRequest {
12178 commands: ReceivePackRequest {
12179 commands: vec![ReceivePackCommand {
12180 old_id: old_id.clone(),
12181 new_id: new_id.clone(),
12182 name: "refs/heads/main".into(),
12183 }],
12184 ..ReceivePackRequest::default()
12185 },
12186 ..ReceivePackPushRequest::default()
12187 };
12188 let installed = std::cell::Cell::new(false);
12189 let applied = std::cell::RefCell::new(Vec::new());
12190
12191 let report = apply_receive_pack_push_request(
12192 &ReceivePackFeatures::default(),
12193 &request,
12194 |_| unreachable!("update stale-old checks belong to the ref transaction callback"),
12195 |_| {
12196 installed.set(true);
12197 Ok(())
12198 },
12199 |oid| Ok(oid == &new_id),
12200 |commands| {
12201 applied.borrow_mut().extend_from_slice(commands);
12202 Ok(())
12203 },
12204 |_| unreachable!("no delete command should be applied"),
12205 )
12206 .expect("test operation should succeed");
12207
12208 assert!(!installed.get());
12209 assert_eq!(applied.into_inner(), request.commands.commands);
12210 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
12211 }
12212
12213 #[test]
12214 fn receive_pack_apply_helper_preserves_delete_only_and_stale_delete_rules() {
12215 let old_id = ObjectId::from_hex(
12216 ObjectFormat::Sha1,
12217 "1111111111111111111111111111111111111111",
12218 )
12219 .expect("test operation should succeed");
12220 let other_id = ObjectId::from_hex(
12221 ObjectFormat::Sha1,
12222 "2222222222222222222222222222222222222222",
12223 )
12224 .expect("test operation should succeed");
12225 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
12226 let request = ReceivePackPushRequest {
12227 commands: ReceivePackRequest {
12228 commands: vec![ReceivePackCommand {
12229 old_id: old_id.clone(),
12230 new_id: zero,
12231 name: "refs/heads/main".into(),
12232 }],
12233 ..ReceivePackRequest::default()
12234 },
12235 ..ReceivePackPushRequest::default()
12236 };
12237 let features = ReceivePackFeatures {
12238 delete_refs: true,
12239 ..ReceivePackFeatures::default()
12240 };
12241 let installed = std::cell::Cell::new(false);
12242 let deleted = std::cell::RefCell::new(Vec::new());
12243
12244 let report = apply_receive_pack_push_request(
12245 &features,
12246 &request,
12247 |_| Ok(Some(old_id.clone())),
12248 |_| {
12249 installed.set(true);
12250 Ok(())
12251 },
12252 |_| Ok(false),
12253 |_| unreachable!("delete-only request should not apply updates"),
12254 |command| {
12255 deleted.borrow_mut().push(command.name.clone());
12256 Ok(())
12257 },
12258 )
12259 .expect("test operation should succeed");
12260
12261 assert!(!installed.get());
12262 assert_eq!(deleted.into_inner(), vec!["refs/heads/main"]);
12263 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
12264 assert!(
12265 apply_receive_pack_push_request(
12266 &features,
12267 &request,
12268 |_| Ok(Some(other_id.clone())),
12269 |_| Ok(()),
12270 |_| Ok(false),
12271 |_| Ok(()),
12272 |_| Ok(()),
12273 )
12274 .is_err()
12275 );
12276 }
12277
12278 #[test]
12279 fn receive_pack_push_request_parses_commands_options_and_packfile() {
12280 let command = ReceivePackCommand {
12281 old_id: ObjectId::from_hex(
12282 ObjectFormat::Sha1,
12283 "1111111111111111111111111111111111111111",
12284 )
12285 .expect("test operation should succeed"),
12286 new_id: ObjectId::from_hex(
12287 ObjectFormat::Sha1,
12288 "2222222222222222222222222222222222222222",
12289 )
12290 .expect("test operation should succeed"),
12291 name: "refs/heads/main".into(),
12292 };
12293 let expected = ReceivePackPushRequest {
12294 commands: ReceivePackRequest {
12295 commands: vec![command],
12296 capabilities: vec![
12297 Capability {
12298 name: "report-status".into(),
12299 value: None,
12300 },
12301 Capability {
12302 name: "push-options".into(),
12303 value: None,
12304 },
12305 ],
12306 ..ReceivePackRequest::default()
12307 },
12308 push_options: Some(vec!["ci.skip".into(), "deploy=staging".into()]),
12309 packfile: b"PACK\x00\x00\x00\x02payload".to_vec(),
12310 };
12311 let encoded =
12312 encode_receive_pack_push_request(&expected).expect("test operation should succeed");
12313
12314 assert_eq!(
12315 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true)
12316 .expect("test operation should succeed"),
12317 expected
12318 );
12319 }
12320
12321 #[test]
12322 fn receive_pack_push_request_preserves_packfile_without_push_options() {
12323 let request = ReceivePackPushRequest {
12324 commands: ReceivePackRequest {
12325 commands: vec![ReceivePackCommand {
12326 old_id: ObjectId::from_hex(
12327 ObjectFormat::Sha1,
12328 "1111111111111111111111111111111111111111",
12329 )
12330 .expect("test operation should succeed"),
12331 new_id: ObjectId::from_hex(
12332 ObjectFormat::Sha1,
12333 "2222222222222222222222222222222222222222",
12334 )
12335 .expect("test operation should succeed"),
12336 name: "refs/heads/main".into(),
12337 }],
12338 ..ReceivePackRequest::default()
12339 },
12340 push_options: None,
12341 packfile: b"0000PACK-like bytes stay raw".to_vec(),
12342 };
12343 let encoded =
12344 encode_receive_pack_push_request(&request).expect("test operation should succeed");
12345
12346 assert_eq!(
12347 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, false)
12348 .expect("test operation should succeed"),
12349 request
12350 );
12351 }
12352
12353 #[test]
12354 fn receive_pack_push_request_streams_round_trip() {
12355 let request = ReceivePackPushRequest {
12356 commands: ReceivePackRequest {
12357 commands: vec![ReceivePackCommand {
12358 old_id: ObjectId::from_hex(
12359 ObjectFormat::Sha1,
12360 "1111111111111111111111111111111111111111",
12361 )
12362 .expect("test operation should succeed"),
12363 new_id: ObjectId::from_hex(
12364 ObjectFormat::Sha1,
12365 "2222222222222222222222222222222222222222",
12366 )
12367 .expect("test operation should succeed"),
12368 name: "refs/heads/main".into(),
12369 }],
12370 capabilities: vec![Capability {
12371 name: "push-options".into(),
12372 value: None,
12373 }],
12374 ..ReceivePackRequest::default()
12375 },
12376 push_options: Some(Vec::new()),
12377 packfile: b"PACKpayload".to_vec(),
12378 };
12379 let mut encoded = Vec::new();
12380 write_receive_pack_push_request(&mut encoded, &request)
12381 .expect("test operation should succeed");
12382
12383 assert_eq!(
12384 read_receive_pack_push_request(ObjectFormat::Sha1, &mut encoded.as_slice(), true)
12385 .expect("test operation should succeed"),
12386 request
12387 );
12388 }
12389
12390 #[test]
12391 fn receive_pack_push_request_rejects_malformed_sections() {
12392 assert!(
12393 parse_receive_pack_push_request(
12394 ObjectFormat::Sha1,
12395 b"0014not-a-command\n0000PACK",
12396 false,
12397 )
12398 .is_err()
12399 );
12400
12401 let request = ReceivePackPushRequest {
12402 commands: ReceivePackRequest {
12403 commands: vec![ReceivePackCommand {
12404 old_id: ObjectId::from_hex(
12405 ObjectFormat::Sha1,
12406 "1111111111111111111111111111111111111111",
12407 )
12408 .expect("test operation should succeed"),
12409 new_id: ObjectId::from_hex(
12410 ObjectFormat::Sha1,
12411 "2222222222222222222222222222222222222222",
12412 )
12413 .expect("test operation should succeed"),
12414 name: "refs/heads/main".into(),
12415 }],
12416 ..ReceivePackRequest::default()
12417 },
12418 push_options: None,
12419 packfile: b"PACKpayload".to_vec(),
12420 };
12421 let encoded =
12422 encode_receive_pack_push_request(&request).expect("test operation should succeed");
12423 assert!(parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true).is_err());
12424
12425 assert!(
12426 encode_receive_pack_push_request(&ReceivePackPushRequest {
12427 commands: ReceivePackRequest {
12428 shallow: vec![
12429 ObjectId::from_hex(
12430 ObjectFormat::Sha1,
12431 "1111111111111111111111111111111111111111",
12432 )
12433 .expect("test operation should succeed")
12434 ],
12435 ..ReceivePackRequest::default()
12436 },
12437 push_options: None,
12438 packfile: Vec::new(),
12439 })
12440 .is_err()
12441 );
12442 }
12443
12444 #[test]
12445 fn receive_pack_report_status_parses_and_encodes_status_lines() {
12446 let frames = vec![
12447 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12448 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12449 PktLineFrame::Data(b"ng refs/heads/old non-fast-forward\n".to_vec()),
12450 PktLineFrame::Flush,
12451 ];
12452 let report =
12453 parse_receive_pack_report_status(&frames).expect("test operation should succeed");
12454 assert_eq!(
12455 report,
12456 ReceivePackReportStatus {
12457 unpack: ReceivePackUnpackStatus::Ok,
12458 commands: vec![
12459 ReceivePackCommandStatus::Ok {
12460 name: "refs/heads/main".into(),
12461 },
12462 ReceivePackCommandStatus::Ng {
12463 name: "refs/heads/old".into(),
12464 message: "non-fast-forward".into(),
12465 },
12466 ],
12467 }
12468 );
12469 assert_eq!(
12470 encode_receive_pack_report_status(&report).expect("test operation should succeed"),
12471 frames
12472 );
12473
12474 let frames = vec![
12475 PktLineFrame::Data(b"unpack pack exceeds maximum size\n".to_vec()),
12476 PktLineFrame::Flush,
12477 ];
12478 assert_eq!(
12479 parse_receive_pack_report_status(&frames).expect("test operation should succeed"),
12480 ReceivePackReportStatus {
12481 unpack: ReceivePackUnpackStatus::Error("pack exceeds maximum size".into()),
12482 commands: Vec::new(),
12483 }
12484 );
12485 }
12486
12487 #[test]
12488 fn receive_pack_report_status_streams_round_trip() {
12489 let report = ReceivePackReportStatus {
12490 unpack: ReceivePackUnpackStatus::Ok,
12491 commands: vec![ReceivePackCommandStatus::Ok {
12492 name: "refs/heads/main".into(),
12493 }],
12494 };
12495 let mut encoded = Vec::new();
12496 write_receive_pack_report_status(&mut encoded, &report)
12497 .expect("test operation should succeed");
12498 encoded.extend_from_slice(b"tail");
12499
12500 let mut input = encoded.as_slice();
12501 assert_eq!(
12502 read_receive_pack_report_status(&mut input).expect("test operation should succeed"),
12503 report
12504 );
12505 assert_eq!(input, b"tail");
12506 }
12507
12508 #[test]
12509 fn receive_pack_report_status_rejects_malformed_status_lines() {
12510 assert!(parse_receive_pack_report_status(&[]).is_err());
12511 assert!(
12512 parse_receive_pack_report_status(&[
12513 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12514 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12515 ])
12516 .is_err()
12517 );
12518 assert!(
12519 parse_receive_pack_report_status(&[
12520 PktLineFrame::Flush,
12521 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12522 ])
12523 .is_err()
12524 );
12525 assert!(
12526 parse_receive_pack_report_status(&[
12527 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12528 PktLineFrame::Data(b"bad refs/heads/main\n".to_vec()),
12529 PktLineFrame::Flush,
12530 ])
12531 .is_err()
12532 );
12533 assert!(
12534 parse_receive_pack_report_status(&[
12535 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12536 PktLineFrame::Data(b"ng refs/heads/main\n".to_vec()),
12537 PktLineFrame::Flush,
12538 ])
12539 .is_err()
12540 );
12541 assert!(
12542 encode_receive_pack_report_status(&ReceivePackReportStatus {
12543 unpack: ReceivePackUnpackStatus::Error("".into()),
12544 commands: Vec::new(),
12545 })
12546 .is_err()
12547 );
12548 assert!(
12549 encode_receive_pack_report_status(&ReceivePackReportStatus {
12550 unpack: ReceivePackUnpackStatus::Ok,
12551 commands: vec![ReceivePackCommandStatus::Ok {
12552 name: "bad ref".into(),
12553 }],
12554 })
12555 .is_err()
12556 );
12557 }
12558
12559 #[test]
12560 fn receive_pack_report_status_v2_parses_and_encodes_options() {
12561 let old_oid = ObjectId::from_hex(
12562 ObjectFormat::Sha1,
12563 "1111111111111111111111111111111111111111",
12564 )
12565 .expect("test operation should succeed");
12566 let new_oid = ObjectId::from_hex(
12567 ObjectFormat::Sha1,
12568 "2222222222222222222222222222222222222222",
12569 )
12570 .expect("test operation should succeed");
12571 let frames = vec![
12572 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12573 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12574 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12575 PktLineFrame::Data(
12576 b"option old-oid 1111111111111111111111111111111111111111\n".to_vec(),
12577 ),
12578 PktLineFrame::Data(
12579 b"option new-oid 2222222222222222222222222222222222222222\n".to_vec(),
12580 ),
12581 PktLineFrame::Data(b"option forced-update\n".to_vec()),
12582 PktLineFrame::Data(b"ng refs/heads/old rejected by hook\n".to_vec()),
12583 PktLineFrame::Flush,
12584 ];
12585 let report = parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &frames)
12586 .expect("test operation should succeed");
12587 assert_eq!(
12588 report,
12589 ReceivePackReportStatusV2 {
12590 unpack: ReceivePackUnpackStatus::Ok,
12591 commands: vec![
12592 ReceivePackCommandStatusV2::Ok {
12593 name: "refs/for/main".into(),
12594 options: ReceivePackCommandStatusV2Options {
12595 refname: Some("refs/heads/main".into()),
12596 old_oid: Some(old_oid),
12597 new_oid: Some(new_oid),
12598 forced_update: true,
12599 },
12600 },
12601 ReceivePackCommandStatusV2::Ng {
12602 name: "refs/heads/old".into(),
12603 message: "rejected by hook".into(),
12604 },
12605 ],
12606 }
12607 );
12608 assert_eq!(
12609 encode_receive_pack_report_status_v2(&report).expect("test operation should succeed"),
12610 frames
12611 );
12612 }
12613
12614 #[test]
12615 fn receive_pack_report_status_v2_streams_round_trip() {
12616 let report = ReceivePackReportStatusV2 {
12617 unpack: ReceivePackUnpackStatus::Ok,
12618 commands: vec![ReceivePackCommandStatusV2::Ok {
12619 name: "refs/for/main".into(),
12620 options: ReceivePackCommandStatusV2Options {
12621 refname: Some("refs/heads/main".into()),
12622 old_oid: None,
12623 new_oid: None,
12624 forced_update: false,
12625 },
12626 }],
12627 };
12628 let mut encoded = Vec::new();
12629 write_receive_pack_report_status_v2(&mut encoded, &report)
12630 .expect("test operation should succeed");
12631 encoded.extend_from_slice(b"tail");
12632
12633 let mut input = encoded.as_slice();
12634 assert_eq!(
12635 read_receive_pack_report_status_v2(ObjectFormat::Sha1, &mut input)
12636 .expect("test operation should succeed"),
12637 report
12638 );
12639 assert_eq!(input, b"tail");
12640 }
12641
12642 #[test]
12643 fn receive_pack_report_status_v2_rejects_malformed_options() {
12644 assert!(parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &[]).is_err());
12645 assert!(
12646 parse_receive_pack_report_status_v2(
12647 ObjectFormat::Sha1,
12648 &[
12649 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12650 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12651 PktLineFrame::Flush,
12652 ],
12653 )
12654 .is_err()
12655 );
12656 assert!(
12657 parse_receive_pack_report_status_v2(
12658 ObjectFormat::Sha1,
12659 &[
12660 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12661 PktLineFrame::Data(b"ng refs/heads/main rejected\n".to_vec()),
12662 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12663 PktLineFrame::Flush,
12664 ],
12665 )
12666 .is_err()
12667 );
12668 assert!(
12669 parse_receive_pack_report_status_v2(
12670 ObjectFormat::Sha1,
12671 &[
12672 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12673 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12674 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12675 PktLineFrame::Data(b"option refname refs/heads/next\n".to_vec()),
12676 PktLineFrame::Flush,
12677 ],
12678 )
12679 .is_err()
12680 );
12681 assert!(
12682 parse_receive_pack_report_status_v2(
12683 ObjectFormat::Sha1,
12684 &[
12685 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12686 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12687 PktLineFrame::Data(b"option old-oid not-an-oid\n".to_vec()),
12688 PktLineFrame::Flush,
12689 ],
12690 )
12691 .is_err()
12692 );
12693 assert!(
12694 encode_receive_pack_report_status_v2(&ReceivePackReportStatusV2 {
12695 unpack: ReceivePackUnpackStatus::Ok,
12696 commands: vec![ReceivePackCommandStatusV2::Ok {
12697 name: "refs/for/main".into(),
12698 options: ReceivePackCommandStatusV2Options {
12699 refname: Some("bad ref".into()),
12700 ..ReceivePackCommandStatusV2Options::default()
12701 },
12702 }],
12703 })
12704 .is_err()
12705 );
12706 }
12707
12708 #[test]
12709 fn receive_pack_push_options_parse_and_encode_options() {
12710 let frames = vec![
12711 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12712 PktLineFrame::Data(b"deploy target=staging\n".to_vec()),
12713 PktLineFrame::Data(b"\n".to_vec()),
12714 PktLineFrame::Flush,
12715 ];
12716 let options =
12717 parse_receive_pack_push_options(&frames).expect("test operation should succeed");
12718 assert_eq!(
12719 options,
12720 vec![
12721 "ci.skip".to_string(),
12722 "deploy target=staging".to_string(),
12723 String::new(),
12724 ]
12725 );
12726 assert_eq!(
12727 encode_receive_pack_push_options(&options).expect("test operation should succeed"),
12728 frames
12729 );
12730 assert_eq!(
12731 parse_receive_pack_push_options(&[PktLineFrame::Flush])
12732 .expect("test operation should succeed"),
12733 Vec::<String>::new()
12734 );
12735 }
12736
12737 #[test]
12738 fn receive_pack_push_options_streams_round_trip() {
12739 let options = vec!["ci.skip".to_string(), "reviewer=alice".to_string()];
12740 let mut encoded = Vec::new();
12741 write_receive_pack_push_options(&mut encoded, &options)
12742 .expect("test operation should succeed");
12743 encoded.extend_from_slice(b"PACK");
12744
12745 let mut input = encoded.as_slice();
12746 assert_eq!(
12747 read_receive_pack_push_options(&mut input).expect("test operation should succeed"),
12748 options
12749 );
12750 assert_eq!(input, b"PACK");
12751 }
12752
12753 #[test]
12754 fn receive_pack_push_options_reject_malformed_streams() {
12755 assert!(
12756 parse_receive_pack_push_options(&[PktLineFrame::Data(b"ci.skip\n".to_vec())]).is_err()
12757 );
12758 assert!(
12759 parse_receive_pack_push_options(&[PktLineFrame::Delimiter, PktLineFrame::Flush])
12760 .is_err()
12761 );
12762 assert!(
12763 parse_receive_pack_push_options(&[
12764 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12765 PktLineFrame::Flush,
12766 PktLineFrame::Data(b"after\n".to_vec()),
12767 ])
12768 .is_err()
12769 );
12770 assert!(
12771 parse_receive_pack_push_options(&[
12772 PktLineFrame::Data(b"bad\0option\n".to_vec()),
12773 PktLineFrame::Flush,
12774 ])
12775 .is_err()
12776 );
12777 assert!(encode_receive_pack_push_options(&["bad\noption".to_string()]).is_err());
12778 }
12779
12780 #[test]
12781 fn protocol_v2_advertisement_parses_version_and_capabilities() {
12782 let frames = parse_pkt_line_stream(
12783 b"000eversion 2\n0015agent=git/2.54.0\n0013ls-refs=unborn\n0027fetch=shallow wait-for-done filter\n0012server-option\n0000",
12784 )
12785 .expect("test operation should succeed");
12786 let handshake =
12787 parse_protocol_v2_advertisement(&frames).expect("test operation should succeed");
12788 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12789 assert_eq!(
12790 handshake.capabilities,
12791 vec![
12792 Capability {
12793 name: "agent".into(),
12794 value: Some("git/2.54.0".into()),
12795 },
12796 Capability {
12797 name: "ls-refs".into(),
12798 value: Some("unborn".into()),
12799 },
12800 Capability {
12801 name: "fetch".into(),
12802 value: Some("shallow wait-for-done filter".into()),
12803 },
12804 Capability {
12805 name: "server-option".into(),
12806 value: None,
12807 },
12808 ]
12809 );
12810 assert_eq!(
12811 encode_protocol_v2_advertisement(&handshake).expect("test operation should succeed"),
12812 frames
12813 );
12814 }
12815
12816 #[test]
12817 fn protocol_v2_advertisement_reads_until_flush() {
12818 let mut input = b"000eversion 2\n0013ls-refs=unborn\n0000next-session".as_slice();
12819 let handshake =
12820 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed");
12821 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12822 assert_eq!(
12823 handshake.capabilities,
12824 vec![Capability {
12825 name: "ls-refs".into(),
12826 value: Some("unborn".into()),
12827 }]
12828 );
12829 assert_eq!(input, b"next-session");
12830 }
12831
12832 #[test]
12833 fn protocol_v2_advertisement_writes_stream() {
12834 let handshake = TransportHandshake {
12835 protocol: ProtocolVersion::V2,
12836 capabilities: vec![
12837 Capability {
12838 name: "agent".into(),
12839 value: Some("sley/0".into()),
12840 },
12841 Capability {
12842 name: "fetch".into(),
12843 value: Some("shallow filter".into()),
12844 },
12845 ],
12846 };
12847 let mut encoded = Vec::new();
12848 write_protocol_v2_advertisement(&mut encoded, &handshake)
12849 .expect("test operation should succeed");
12850 let mut input = encoded.as_slice();
12851 assert_eq!(
12852 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed"),
12853 handshake
12854 );
12855 assert!(input.is_empty());
12856 assert!(
12857 encode_protocol_v2_advertisement(&TransportHandshake {
12858 protocol: ProtocolVersion::V1,
12859 capabilities: Vec::new(),
12860 })
12861 .is_err()
12862 );
12863 }
12864
12865 #[test]
12866 fn protocol_v2_advertisement_rejects_malformed_sequences() {
12867 assert!(parse_protocol_v2_advertisement(&[]).is_err());
12868 assert!(
12869 parse_protocol_v2_advertisement(&[
12870 PktLineFrame::Data(b"version 1\n".to_vec()),
12871 PktLineFrame::Flush,
12872 ])
12873 .is_err()
12874 );
12875 assert!(
12876 parse_protocol_v2_advertisement(&[PktLineFrame::Data(b"version 2\n".to_vec())])
12877 .is_err()
12878 );
12879 assert!(
12880 parse_protocol_v2_advertisement(&[
12881 PktLineFrame::Data(b"version 2\n".to_vec()),
12882 PktLineFrame::Delimiter,
12883 ])
12884 .is_err()
12885 );
12886 assert!(
12887 parse_protocol_v2_advertisement(&[
12888 PktLineFrame::Data(b"version 2\n".to_vec()),
12889 PktLineFrame::Data(b"fetch=\n".to_vec()),
12890 PktLineFrame::Flush,
12891 ])
12892 .is_err()
12893 );
12894 }
12895
12896 #[test]
12897 fn protocol_v2_command_request_parses_and_encodes_sections() {
12898 let frames = parse_pkt_line_stream(
12899 b"0014command=ls-refs\n0011agent=sley/0\n0017object-format=sha1\n00010009peel\n000csymrefs\n001bref-prefix refs/heads/\n0000",
12900 )
12901 .expect("test operation should succeed");
12902 let request =
12903 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12904 assert_eq!(
12905 request,
12906 ProtocolV2CommandRequest {
12907 command: "ls-refs".into(),
12908 capabilities: vec![
12909 Capability {
12910 name: "agent".into(),
12911 value: Some("sley/0".into()),
12912 },
12913 Capability {
12914 name: "object-format".into(),
12915 value: Some("sha1".into()),
12916 },
12917 ],
12918 arguments: vec![
12919 b"peel".to_vec(),
12920 b"symrefs".to_vec(),
12921 b"ref-prefix refs/heads/".to_vec(),
12922 ],
12923 }
12924 );
12925 assert_eq!(
12926 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12927 frames
12928 );
12929 }
12930
12931 #[test]
12932 fn protocol_v2_command_request_allows_no_argument_section() {
12933 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12934 .expect("test operation should succeed");
12935 let request =
12936 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12937 assert_eq!(
12938 request,
12939 ProtocolV2CommandRequest {
12940 command: "fetch".into(),
12941 capabilities: Vec::new(),
12942 arguments: Vec::new(),
12943 }
12944 );
12945 assert_eq!(
12946 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12947 frames
12948 );
12949 }
12950
12951 #[test]
12952 fn protocol_v2_request_parses_commands_and_empty_done() {
12953 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12954 .expect("test operation should succeed");
12955 let command = ProtocolV2CommandRequest {
12956 command: "fetch".into(),
12957 capabilities: Vec::new(),
12958 arguments: Vec::new(),
12959 };
12960 assert_eq!(
12961 parse_protocol_v2_request(&frames).expect("test operation should succeed"),
12962 ProtocolV2Request::Command(command.clone())
12963 );
12964 assert_eq!(
12965 encode_protocol_v2_request(&ProtocolV2Request::Command(command))
12966 .expect("test operation should succeed"),
12967 frames
12968 );
12969
12970 assert_eq!(
12971 parse_protocol_v2_request(&[PktLineFrame::Flush])
12972 .expect("test operation should succeed"),
12973 ProtocolV2Request::Done
12974 );
12975 assert_eq!(
12976 encode_protocol_v2_request(&ProtocolV2Request::Done)
12977 .expect("test operation should succeed"),
12978 vec![PktLineFrame::Flush]
12979 );
12980 }
12981
12982 #[test]
12983 fn protocol_v2_request_streams_empty_done() {
12984 let mut encoded = Vec::new();
12985 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
12986 .expect("test operation should succeed");
12987 encoded.extend_from_slice(b"tail");
12988
12989 let mut input = encoded.as_slice();
12990 assert_eq!(
12991 read_protocol_v2_request(&mut input).expect("test operation should succeed"),
12992 ProtocolV2Request::Done
12993 );
12994 assert_eq!(input, b"tail");
12995 let mut command_input = encoded.as_slice();
12996 assert!(read_protocol_v2_command_request(&mut command_input).is_err());
12997 }
12998
12999 #[test]
13000 fn protocol_v2_command_request_streams_round_trip() {
13001 let request = ProtocolV2CommandRequest {
13002 command: "ls-refs".into(),
13003 capabilities: vec![Capability {
13004 name: "agent".into(),
13005 value: Some("sley/0".into()),
13006 }],
13007 arguments: vec![b"peel".to_vec(), b"symrefs".to_vec()],
13008 };
13009 let mut encoded = Vec::new();
13010 write_protocol_v2_command_request(&mut encoded, &request)
13011 .expect("test operation should succeed");
13012 encoded.extend_from_slice(b"tail");
13013
13014 let mut input = encoded.as_slice();
13015 assert_eq!(
13016 read_protocol_v2_command_request(&mut input).expect("test operation should succeed"),
13017 request
13018 );
13019 assert_eq!(input, b"tail");
13020 }
13021
13022 #[test]
13023 fn protocol_v2_command_request_rejects_malformed_sequences() {
13024 assert!(parse_protocol_v2_command_request(&[]).is_err());
13025 assert!(
13026 parse_protocol_v2_command_request(&[
13027 PktLineFrame::Data(b"agent=sley/0\n".to_vec()),
13028 PktLineFrame::Flush,
13029 ])
13030 .is_err()
13031 );
13032 assert!(
13033 parse_protocol_v2_command_request(&[
13034 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
13035 PktLineFrame::Delimiter,
13036 PktLineFrame::Delimiter,
13037 PktLineFrame::Flush,
13038 ])
13039 .is_err()
13040 );
13041 assert!(
13042 parse_protocol_v2_command_request(&[
13043 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
13044 PktLineFrame::Delimiter,
13045 PktLineFrame::Data(b"\n".to_vec()),
13046 PktLineFrame::Flush,
13047 ])
13048 .is_err()
13049 );
13050 assert!(
13051 encode_protocol_v2_command_request(&ProtocolV2CommandRequest {
13052 command: "bad command".into(),
13053 capabilities: Vec::new(),
13054 arguments: Vec::new(),
13055 })
13056 .is_err()
13057 );
13058 }
13059
13060 #[test]
13061 fn protocol_v2_ls_refs_request_parses_and_encodes_arguments() {
13062 let command = ProtocolV2CommandRequest {
13063 command: "ls-refs".into(),
13064 capabilities: Vec::new(),
13065 arguments: vec![
13066 b"peel".to_vec(),
13067 b"symrefs".to_vec(),
13068 b"unborn".to_vec(),
13069 b"ref-prefix HEAD".to_vec(),
13070 b"ref-prefix refs/heads/".to_vec(),
13071 ],
13072 };
13073 let request = ProtocolV2LsRefsRequest::from_command_request(&command)
13074 .expect("test operation should succeed");
13075 assert_eq!(
13076 request,
13077 ProtocolV2LsRefsRequest {
13078 peel: true,
13079 symrefs: true,
13080 unborn: true,
13081 ref_prefixes: vec!["HEAD".into(), "refs/heads/".into()],
13082 }
13083 );
13084 assert_eq!(
13085 request
13086 .to_command_request()
13087 .expect("test operation should succeed"),
13088 command
13089 );
13090 assert!(
13091 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
13092 command: "fetch".into(),
13093 capabilities: Vec::new(),
13094 arguments: Vec::new(),
13095 })
13096 .is_err()
13097 );
13098 assert!(
13099 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
13100 command: "ls-refs".into(),
13101 capabilities: Vec::new(),
13102 arguments: vec![b"ref-prefix ".to_vec()],
13103 })
13104 .is_err()
13105 );
13106 }
13107
13108 #[test]
13109 fn protocol_v2_ls_refs_request_streams_round_trip() {
13110 let request = ProtocolV2LsRefsRequest {
13111 peel: true,
13112 symrefs: true,
13113 unborn: false,
13114 ref_prefixes: vec!["HEAD".into(), "refs/tags/".into()],
13115 };
13116 let mut encoded = Vec::new();
13117 write_protocol_v2_ls_refs_request(&mut encoded, &request)
13118 .expect("test operation should succeed");
13119 encoded.extend_from_slice(b"tail");
13120
13121 let mut input = encoded.as_slice();
13122 assert_eq!(
13123 read_protocol_v2_ls_refs_request(&mut input).expect("test operation should succeed"),
13124 request
13125 );
13126 assert_eq!(input, b"tail");
13127 }
13128
13129 #[test]
13130 fn protocol_v2_ls_refs_response_parses_and_encodes_records() {
13131 let oid = ObjectId::from_hex(
13132 ObjectFormat::Sha1,
13133 "1111111111111111111111111111111111111111",
13134 )
13135 .expect("test operation should succeed");
13136 let peeled = ObjectId::from_hex(
13137 ObjectFormat::Sha1,
13138 "2222222222222222222222222222222222222222",
13139 )
13140 .expect("test operation should succeed");
13141 let frames = vec![
13142 PktLineFrame::Data(
13143 b"1111111111111111111111111111111111111111 refs/tags/v1 peeled:2222222222222222222222222222222222222222 symref-target:refs/heads/main custom\n"
13144 .to_vec(),
13145 ),
13146 PktLineFrame::Data(b"unborn HEAD symref-target:refs/heads/main\n".to_vec()),
13147 PktLineFrame::Flush,
13148 ];
13149 let records = parse_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &frames)
13150 .expect("test operation should succeed");
13151 assert_eq!(
13152 records,
13153 vec![
13154 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13155 oid,
13156 name: "refs/tags/v1".into(),
13157 peeled: Some(peeled),
13158 symref_target: Some("refs/heads/main".into()),
13159 attributes: vec!["custom".into()],
13160 }),
13161 ProtocolV2LsRefsRecord::Unborn {
13162 name: "HEAD".into(),
13163 symref_target: Some("refs/heads/main".into()),
13164 attributes: Vec::new(),
13165 },
13166 ]
13167 );
13168 assert_eq!(
13169 encode_protocol_v2_ls_refs_response(&records).expect("test operation should succeed"),
13170 frames
13171 );
13172 }
13173
13174 #[test]
13175 fn protocol_v2_ls_refs_response_streams_round_trip() {
13176 let oid = ObjectId::from_hex(
13177 ObjectFormat::Sha1,
13178 "1111111111111111111111111111111111111111",
13179 )
13180 .expect("test operation should succeed");
13181 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13182 oid,
13183 name: "refs/heads/main".into(),
13184 peeled: None,
13185 symref_target: Some("refs/heads/trunk".into()),
13186 attributes: vec!["custom".into()],
13187 })];
13188 let mut encoded = Vec::new();
13189 write_protocol_v2_ls_refs_response(&mut encoded, &records)
13190 .expect("test operation should succeed");
13191 encoded.extend_from_slice(b"tail");
13192
13193 let mut input = encoded.as_slice();
13194 assert_eq!(
13195 read_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &mut input)
13196 .expect("test operation should succeed"),
13197 records
13198 );
13199 assert_eq!(input, b"tail");
13200 }
13201
13202 #[test]
13203 fn protocol_v2_ls_refs_response_reads_stateless_response_end() {
13204 let oid = ObjectId::from_hex(
13205 ObjectFormat::Sha1,
13206 "1111111111111111111111111111111111111111",
13207 )
13208 .expect("test operation should succeed");
13209 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13210 oid,
13211 name: "refs/heads/main".into(),
13212 peeled: None,
13213 symref_target: None,
13214 attributes: Vec::new(),
13215 })];
13216 let mut encoded = Vec::new();
13217 write_protocol_v2_ls_refs_response_with_response_end(&mut encoded, &records)
13218 .expect("test operation should succeed");
13219 encoded.extend_from_slice(b"tail");
13220
13221 let mut input = encoded.as_slice();
13222 assert_eq!(
13223 read_protocol_v2_ls_refs_response_until_response_end(ObjectFormat::Sha1, &mut input)
13224 .expect("test operation should succeed"),
13225 records
13226 );
13227 assert_eq!(input, b"tail");
13228 assert!(
13229 parse_protocol_v2_ls_refs_response(
13230 ObjectFormat::Sha1,
13231 &[
13232 PktLineFrame::Data(
13233 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
13234 ),
13235 PktLineFrame::ResponseEnd
13236 ],
13237 )
13238 .is_err()
13239 );
13240 }
13241
13242 #[test]
13243 fn protocol_v2_ls_refs_exchange_writes_request_and_reads_response() {
13244 let oid = ObjectId::from_hex(
13245 ObjectFormat::Sha1,
13246 "1111111111111111111111111111111111111111",
13247 )
13248 .expect("test operation should succeed");
13249 let request = ProtocolV2LsRefsRequest {
13250 peel: true,
13251 symrefs: true,
13252 unborn: false,
13253 ref_prefixes: vec!["refs/heads/".into()],
13254 };
13255 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13256 oid,
13257 name: "refs/heads/main".into(),
13258 peeled: None,
13259 symref_target: None,
13260 attributes: Vec::new(),
13261 })];
13262 let mut response = Vec::new();
13263 write_protocol_v2_ls_refs_response(&mut response, &records)
13264 .expect("test operation should succeed");
13265
13266 let mut input = response.as_slice();
13267 let mut output = Vec::new();
13268 assert_eq!(
13269 exchange_protocol_v2_ls_refs(ObjectFormat::Sha1, &mut input, &mut output, &request)
13270 .expect("test operation should succeed"),
13271 records
13272 );
13273 assert!(input.is_empty());
13274 let mut output_read = output.as_slice();
13275 assert_eq!(
13276 read_protocol_v2_ls_refs_request(&mut output_read)
13277 .expect("test operation should succeed"),
13278 request
13279 );
13280 }
13281
13282 #[test]
13283 fn protocol_v2_ls_refs_response_rejects_malformed_records() {
13284 assert!(
13285 parse_protocol_v2_ls_refs_response(
13286 ObjectFormat::Sha1,
13287 &[PktLineFrame::Data(
13288 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
13289 )],
13290 )
13291 .is_err()
13292 );
13293 assert!(
13294 parse_protocol_v2_ls_refs_response(
13295 ObjectFormat::Sha1,
13296 &[
13297 PktLineFrame::Data(
13298 b"1111111111111111111111111111111111111111 refs/heads/main peeled:2222222222222222222222222222222222222222 peeled:3333333333333333333333333333333333333333\n"
13299 .to_vec()
13300 ),
13301 PktLineFrame::Flush,
13302 ],
13303 )
13304 .is_err()
13305 );
13306 assert!(
13307 parse_protocol_v2_ls_refs_response(
13308 ObjectFormat::Sha1,
13309 &[
13310 PktLineFrame::Data(
13311 b"unborn HEAD peeled:2222222222222222222222222222222222222222\n".to_vec()
13312 ),
13313 PktLineFrame::Flush,
13314 ],
13315 )
13316 .is_err()
13317 );
13318 assert!(
13319 encode_protocol_v2_ls_refs_response(&[ProtocolV2LsRefsRecord::Ref(
13320 ProtocolV2LsRefsRef {
13321 oid: ObjectId::from_hex(
13322 ObjectFormat::Sha1,
13323 "1111111111111111111111111111111111111111",
13324 )
13325 .expect("test operation should succeed"),
13326 name: "refs/heads/main".into(),
13327 peeled: None,
13328 symref_target: None,
13329 attributes: vec!["peeled:2222222222222222222222222222222222222222".into()],
13330 }
13331 )])
13332 .is_err()
13333 );
13334 }
13335
13336 #[test]
13337 fn protocol_v2_fetch_request_parses_and_encodes_arguments() {
13338 let want = ObjectId::from_hex(
13339 ObjectFormat::Sha1,
13340 "1111111111111111111111111111111111111111",
13341 )
13342 .expect("test operation should succeed");
13343 let have = ObjectId::from_hex(
13344 ObjectFormat::Sha1,
13345 "2222222222222222222222222222222222222222",
13346 )
13347 .expect("test operation should succeed");
13348 let shallow = ObjectId::from_hex(
13349 ObjectFormat::Sha1,
13350 "3333333333333333333333333333333333333333",
13351 )
13352 .expect("test operation should succeed");
13353 let command = ProtocolV2CommandRequest {
13354 command: "fetch".into(),
13355 capabilities: Vec::new(),
13356 arguments: vec![
13357 b"want 1111111111111111111111111111111111111111".to_vec(),
13358 b"want-ref refs/heads/main".to_vec(),
13359 b"have 2222222222222222222222222222222222222222".to_vec(),
13360 b"shallow 3333333333333333333333333333333333333333".to_vec(),
13361 b"deepen 10".to_vec(),
13362 b"deepen-since 123456789".to_vec(),
13363 b"deepen-not refs/tags/v1".to_vec(),
13364 b"deepen-relative".to_vec(),
13365 b"filter blob:none".to_vec(),
13366 b"packfile-uris http,https".to_vec(),
13367 b"thin-pack".to_vec(),
13368 b"no-progress".to_vec(),
13369 b"include-tag".to_vec(),
13370 b"ofs-delta".to_vec(),
13371 b"sideband-all".to_vec(),
13372 b"wait-for-done".to_vec(),
13373 b"done".to_vec(),
13374 ],
13375 };
13376 let request = ProtocolV2FetchRequest::from_command_request(ObjectFormat::Sha1, &command)
13377 .expect("test operation should succeed");
13378 assert_eq!(
13379 request,
13380 ProtocolV2FetchRequest {
13381 wants: vec![want],
13382 want_refs: vec!["refs/heads/main".into()],
13383 haves: vec![have],
13384 shallow: vec![shallow],
13385 deepen: Some(10),
13386 deepen_since: Some(123456789),
13387 deepen_not: vec!["refs/tags/v1".into()],
13388 deepen_relative: true,
13389 filter: Some("blob:none".into()),
13390 packfile_uris: Some("http,https".into()),
13391 thin_pack: true,
13392 no_progress: true,
13393 include_tag: true,
13394 ofs_delta: true,
13395 sideband_all: true,
13396 wait_for_done: true,
13397 done: true,
13398 }
13399 );
13400 assert_eq!(
13401 request
13402 .to_command_request()
13403 .expect("test operation should succeed"),
13404 command
13405 );
13406 }
13407
13408 #[test]
13409 fn protocol_v2_fetch_request_rejects_malformed_arguments() {
13410 assert!(
13411 ProtocolV2FetchRequest::from_command_request(
13412 ObjectFormat::Sha1,
13413 &ProtocolV2CommandRequest {
13414 command: "ls-refs".into(),
13415 capabilities: Vec::new(),
13416 arguments: Vec::new(),
13417 },
13418 )
13419 .is_err()
13420 );
13421 assert!(
13422 ProtocolV2FetchRequest::from_command_request(
13423 ObjectFormat::Sha1,
13424 &ProtocolV2CommandRequest {
13425 command: "fetch".into(),
13426 capabilities: Vec::new(),
13427 arguments: vec![b"want not-an-oid".to_vec()],
13428 },
13429 )
13430 .is_err()
13431 );
13432 assert!(
13433 ProtocolV2FetchRequest::from_command_request(
13434 ObjectFormat::Sha1,
13435 &ProtocolV2CommandRequest {
13436 command: "fetch".into(),
13437 capabilities: Vec::new(),
13438 arguments: vec![b"deepen 0".to_vec()],
13439 },
13440 )
13441 .is_err()
13442 );
13443 assert!(
13444 ProtocolV2FetchRequest::from_command_request(
13445 ObjectFormat::Sha1,
13446 &ProtocolV2CommandRequest {
13447 command: "fetch".into(),
13448 capabilities: Vec::new(),
13449 arguments: vec![b"filter blob:none".to_vec(), b"filter tree:0".to_vec()],
13450 },
13451 )
13452 .is_err()
13453 );
13454 assert!(
13455 ProtocolV2FetchRequest {
13456 deepen: Some(0),
13457 ..ProtocolV2FetchRequest::default()
13458 }
13459 .to_command_request()
13460 .is_err()
13461 );
13462 }
13463
13464 #[test]
13465 fn protocol_v2_fetch_request_streams_round_trip() {
13466 let want = ObjectId::from_hex(
13467 ObjectFormat::Sha1,
13468 "1111111111111111111111111111111111111111",
13469 )
13470 .expect("test operation should succeed");
13471 let have = ObjectId::from_hex(
13472 ObjectFormat::Sha1,
13473 "2222222222222222222222222222222222222222",
13474 )
13475 .expect("test operation should succeed");
13476 let request = ProtocolV2FetchRequest {
13477 wants: vec![want],
13478 haves: vec![have],
13479 deepen: Some(5),
13480 filter: Some("blob:none".into()),
13481 thin_pack: true,
13482 done: true,
13483 ..ProtocolV2FetchRequest::default()
13484 };
13485 let mut encoded = Vec::new();
13486 write_protocol_v2_fetch_request(&mut encoded, &request)
13487 .expect("test operation should succeed");
13488 encoded.extend_from_slice(b"tail");
13489
13490 let mut input = encoded.as_slice();
13491 assert_eq!(
13492 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut input)
13493 .expect("test operation should succeed"),
13494 request
13495 );
13496 assert_eq!(input, b"tail");
13497 }
13498
13499 #[test]
13500 fn protocol_v2_fetch_response_parses_and_encodes_sections() {
13501 let ack = ObjectId::from_hex(
13502 ObjectFormat::Sha1,
13503 "1111111111111111111111111111111111111111",
13504 )
13505 .expect("test operation should succeed");
13506 let shallow = ObjectId::from_hex(
13507 ObjectFormat::Sha1,
13508 "2222222222222222222222222222222222222222",
13509 )
13510 .expect("test operation should succeed");
13511 let wanted = ObjectId::from_hex(
13512 ObjectFormat::Sha1,
13513 "3333333333333333333333333333333333333333",
13514 )
13515 .expect("test operation should succeed");
13516 let pack_hash = ObjectId::from_hex(
13517 ObjectFormat::Sha1,
13518 "4444444444444444444444444444444444444444",
13519 )
13520 .expect("test operation should succeed");
13521 let frames = vec![
13522 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13523 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111\n".to_vec()),
13524 PktLineFrame::Data(b"ready\n".to_vec()),
13525 PktLineFrame::Delimiter,
13526 PktLineFrame::Data(b"shallow-info\n".to_vec()),
13527 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
13528 PktLineFrame::Delimiter,
13529 PktLineFrame::Data(b"wanted-refs\n".to_vec()),
13530 PktLineFrame::Data(
13531 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
13532 ),
13533 PktLineFrame::Delimiter,
13534 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13535 PktLineFrame::Data(
13536 b"4444444444444444444444444444444444444444 https://example.invalid/pack-a.pack\n"
13537 .to_vec(),
13538 ),
13539 PktLineFrame::Delimiter,
13540 PktLineFrame::Data(b"packfile\n".to_vec()),
13541 PktLineFrame::Data(b"\x01PACK bytes".to_vec()),
13542 PktLineFrame::Flush,
13543 ];
13544 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13545 .expect("test operation should succeed");
13546 assert_eq!(
13547 sections,
13548 vec![
13549 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13550 ProtocolV2FetchAcknowledgment::Ack(ack),
13551 ProtocolV2FetchAcknowledgment::Ready,
13552 ]),
13553 ProtocolV2FetchResponseSection::ShallowInfo(vec![
13554 ProtocolV2FetchShallowInfo::Shallow(shallow)
13555 ]),
13556 ProtocolV2FetchResponseSection::WantedRefs(vec![ProtocolV2FetchWantedRef {
13557 oid: wanted,
13558 name: "refs/heads/main".into(),
13559 }]),
13560 ProtocolV2FetchResponseSection::PackfileUris(vec![ProtocolV2FetchPackfileUri {
13561 pack_hash,
13562 uri: "https://example.invalid/pack-a.pack".into(),
13563 }]),
13564 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13565 ]
13566 );
13567 assert_eq!(
13568 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13569 frames
13570 );
13571 }
13572
13573 #[test]
13574 fn protocol_v2_fetch_response_preserves_unknown_sections() {
13575 let frames = vec![
13576 PktLineFrame::Data(b"server-feature\n".to_vec()),
13577 PktLineFrame::Data(b"opaque line\n".to_vec()),
13578 PktLineFrame::Flush,
13579 ];
13580 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13581 .expect("test operation should succeed");
13582 assert_eq!(
13583 sections,
13584 vec![ProtocolV2FetchResponseSection::Unknown {
13585 name: "server-feature".into(),
13586 lines: vec![b"opaque line\n".to_vec()],
13587 }]
13588 );
13589 assert_eq!(
13590 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13591 frames
13592 );
13593 }
13594
13595 #[test]
13596 fn protocol_v2_fetch_response_streams_round_trip() {
13597 let ack = ObjectId::from_hex(
13598 ObjectFormat::Sha1,
13599 "1111111111111111111111111111111111111111",
13600 )
13601 .expect("test operation should succeed");
13602 let sections = vec![
13603 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13604 ProtocolV2FetchAcknowledgment::Ack(ack),
13605 ProtocolV2FetchAcknowledgment::Ready,
13606 ]),
13607 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13608 ];
13609 let mut encoded = Vec::new();
13610 write_protocol_v2_fetch_response(&mut encoded, §ions)
13611 .expect("test operation should succeed");
13612 encoded.extend_from_slice(b"tail");
13613
13614 let mut input = encoded.as_slice();
13615 assert_eq!(
13616 read_protocol_v2_fetch_response(ObjectFormat::Sha1, &mut input)
13617 .expect("test operation should succeed"),
13618 sections
13619 );
13620 assert_eq!(input, b"tail");
13621 }
13622
13623 #[test]
13624 fn protocol_v2_fetch_sideband_all_response_parses_sections_and_progress() {
13625 let frames = vec![
13626 PktLineFrame::Data(
13627 encode_sideband_packet(&SideBandPacket {
13628 channel: SideBandChannel::Data,
13629 data: b"acknowledgments\n".to_vec(),
13630 })
13631 .expect("test operation should succeed"),
13632 ),
13633 PktLineFrame::Data(
13634 encode_sideband_packet(&SideBandPacket {
13635 channel: SideBandChannel::Data,
13636 data: b"NAK\n".to_vec(),
13637 })
13638 .expect("test operation should succeed"),
13639 ),
13640 PktLineFrame::Data(
13641 encode_sideband_packet(&SideBandPacket {
13642 channel: SideBandChannel::Progress,
13643 data: b"keepalive\n".to_vec(),
13644 })
13645 .expect("test operation should succeed"),
13646 ),
13647 PktLineFrame::Delimiter,
13648 PktLineFrame::Data(
13649 encode_sideband_packet(&SideBandPacket {
13650 channel: SideBandChannel::Data,
13651 data: b"packfile\n".to_vec(),
13652 })
13653 .expect("test operation should succeed"),
13654 ),
13655 PktLineFrame::Data(b"\x01PACK".to_vec()),
13656 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
13657 PktLineFrame::Flush,
13658 ];
13659
13660 let response = parse_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &frames)
13661 .expect("test operation should succeed");
13662 assert_eq!(
13663 response,
13664 ProtocolV2FetchSidebandAllResponse {
13665 sections: vec![
13666 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13667 ProtocolV2FetchAcknowledgment::Nak
13668 ]),
13669 ProtocolV2FetchResponseSection::Packfile(vec![
13670 b"\x01PACK".to_vec(),
13671 b"\x02counting objects\n".to_vec(),
13672 ]),
13673 ],
13674 progress: vec![b"keepalive\n".to_vec()],
13675 }
13676 );
13677 assert_eq!(
13678 demux_protocol_v2_fetch_packfile(&response.sections)
13679 .expect("test operation should succeed"),
13680 Some(SideBandDemux {
13681 data: b"PACK".to_vec(),
13682 progress: vec![b"counting objects\n".to_vec()],
13683 })
13684 );
13685 }
13686
13687 #[test]
13688 fn protocol_v2_fetch_sideband_all_response_streams_round_trip() {
13689 let sections = vec![
13690 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13691 ProtocolV2FetchAcknowledgment::Nak,
13692 ]),
13693 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13694 ];
13695 let mut encoded = Vec::new();
13696 write_protocol_v2_fetch_sideband_all_response(&mut encoded, §ions)
13697 .expect("test operation should succeed");
13698 encoded.extend_from_slice(b"tail");
13699
13700 let mut input = encoded.as_slice();
13701 assert_eq!(
13702 read_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &mut input)
13703 .expect("test operation should succeed"),
13704 ProtocolV2FetchSidebandAllResponse {
13705 sections: sections.clone(),
13706 progress: Vec::new(),
13707 }
13708 );
13709 assert_eq!(input, b"tail");
13710
13711 let mut encoded = Vec::new();
13712 write_protocol_v2_fetch_sideband_all_response_with_response_end(&mut encoded, §ions)
13713 .expect("test operation should succeed");
13714 encoded.extend_from_slice(b"tail");
13715
13716 let mut input = encoded.as_slice();
13717 assert_eq!(
13718 read_protocol_v2_fetch_sideband_all_response_until_response_end(
13719 ObjectFormat::Sha1,
13720 &mut input,
13721 )
13722 .expect("test operation should succeed")
13723 .sections,
13724 sections
13725 );
13726 assert_eq!(input, b"tail");
13727 }
13728
13729 #[test]
13730 fn protocol_v2_fetch_sideband_all_response_rejects_malformed_sideband() {
13731 assert!(
13732 parse_protocol_v2_fetch_sideband_all_response(
13733 ObjectFormat::Sha1,
13734 &[
13735 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13736 PktLineFrame::Flush,
13737 ],
13738 )
13739 .is_err()
13740 );
13741 assert!(
13742 parse_protocol_v2_fetch_sideband_all_response(
13743 ObjectFormat::Sha1,
13744 &[
13745 PktLineFrame::Data(
13746 encode_sideband_packet(&SideBandPacket {
13747 channel: SideBandChannel::Fatal,
13748 data: b"remote died\n".to_vec(),
13749 })
13750 .expect("test operation should succeed"),
13751 ),
13752 PktLineFrame::Flush,
13753 ],
13754 )
13755 .is_err()
13756 );
13757 }
13758
13759 #[test]
13760 fn protocol_v2_object_info_response_parses_and_encodes_size_records() {
13761 let oid = ObjectId::from_hex(
13762 ObjectFormat::Sha1,
13763 "1111111111111111111111111111111111111111",
13764 )
13765 .expect("test operation should succeed");
13766 let frames = vec![
13767 PktLineFrame::Data(b"size\n".to_vec()),
13768 PktLineFrame::Data(b"1111111111111111111111111111111111111111 12345\n".to_vec()),
13769 PktLineFrame::Flush,
13770 ];
13771 let response = parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &frames)
13772 .expect("test operation should succeed");
13773 assert_eq!(
13774 response,
13775 ProtocolV2ObjectInfoResponse {
13776 size: true,
13777 records: vec![ProtocolV2ObjectInfoRecord { oid, size: 12345 }],
13778 }
13779 );
13780 assert_eq!(
13781 encode_protocol_v2_object_info_response(&response)
13782 .expect("test operation should succeed"),
13783 frames
13784 );
13785 }
13786
13787 #[test]
13788 fn protocol_v2_object_info_response_streams_and_exchanges() {
13789 let request = ProtocolV2ObjectInfoRequest {
13790 size: true,
13791 oids: vec![
13792 ObjectId::from_hex(
13793 ObjectFormat::Sha1,
13794 "1111111111111111111111111111111111111111",
13795 )
13796 .expect("test operation should succeed"),
13797 ],
13798 };
13799 let response = ProtocolV2ObjectInfoResponse {
13800 size: true,
13801 records: vec![ProtocolV2ObjectInfoRecord {
13802 oid: request.oids[0].clone(),
13803 size: 7,
13804 }],
13805 };
13806
13807 let mut encoded = Vec::new();
13808 write_protocol_v2_object_info_response(&mut encoded, &response)
13809 .expect("test operation should succeed");
13810 encoded.extend_from_slice(b"tail");
13811 let mut input = encoded.as_slice();
13812 assert_eq!(
13813 read_protocol_v2_object_info_response(ObjectFormat::Sha1, &mut input)
13814 .expect("test operation should succeed"),
13815 response
13816 );
13817 assert_eq!(input, b"tail");
13818
13819 let mut response_bytes = Vec::new();
13820 write_protocol_v2_object_info_response(&mut response_bytes, &response)
13821 .expect("test operation should succeed");
13822 let mut input = response_bytes.as_slice();
13823 let mut output = Vec::new();
13824 assert_eq!(
13825 exchange_protocol_v2_object_info(
13826 ObjectFormat::Sha1,
13827 &mut input,
13828 &mut output,
13829 &request,
13830 )
13831 .expect("test operation should succeed"),
13832 response
13833 );
13834 assert!(input.is_empty());
13835 let mut output_read = output.as_slice();
13836 assert_eq!(
13837 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut output_read)
13838 .expect("test operation should succeed"),
13839 request
13840 );
13841 }
13842
13843 #[test]
13844 fn protocol_v2_object_info_response_rejects_malformed_records() {
13845 assert!(parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &[]).is_err());
13846 assert!(
13847 parse_protocol_v2_object_info_response(
13848 ObjectFormat::Sha1,
13849 &[PktLineFrame::Data(b"size\n".to_vec())],
13850 )
13851 .is_err()
13852 );
13853 assert!(
13854 parse_protocol_v2_object_info_response(
13855 ObjectFormat::Sha1,
13856 &[PktLineFrame::Data(b"type\n".to_vec()), PktLineFrame::Flush,],
13857 )
13858 .is_err()
13859 );
13860 assert!(
13861 parse_protocol_v2_object_info_response(
13862 ObjectFormat::Sha1,
13863 &[
13864 PktLineFrame::Data(b"size\n".to_vec()),
13865 PktLineFrame::Data(
13866 b"1111111111111111111111111111111111111111 not-a-size\n".to_vec()
13867 ),
13868 PktLineFrame::Flush,
13869 ],
13870 )
13871 .is_err()
13872 );
13873 assert!(
13874 parse_protocol_v2_object_info_response(
13875 ObjectFormat::Sha1,
13876 &[
13877 PktLineFrame::Data(b"size\n".to_vec()),
13878 PktLineFrame::Delimiter,
13879 PktLineFrame::Flush,
13880 ],
13881 )
13882 .is_err()
13883 );
13884 assert!(
13885 encode_protocol_v2_object_info_response(&ProtocolV2ObjectInfoResponse {
13886 size: false,
13887 records: Vec::new(),
13888 })
13889 .is_err()
13890 );
13891 }
13892
13893 #[test]
13894 fn protocol_v2_fetch_response_reads_stateless_response_end() {
13895 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13896 ProtocolV2FetchAcknowledgment::Nak,
13897 ])];
13898 let mut encoded = Vec::new();
13899 write_protocol_v2_fetch_response_with_response_end(&mut encoded, §ions)
13900 .expect("test operation should succeed");
13901 encoded.extend_from_slice(b"tail");
13902
13903 let mut input = encoded.as_slice();
13904 assert_eq!(
13905 read_protocol_v2_fetch_response_until_response_end(ObjectFormat::Sha1, &mut input)
13906 .expect("test operation should succeed"),
13907 sections
13908 );
13909 assert_eq!(input, b"tail");
13910 assert!(
13911 parse_protocol_v2_fetch_response(
13912 ObjectFormat::Sha1,
13913 &[
13914 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13915 PktLineFrame::ResponseEnd,
13916 ],
13917 )
13918 .is_err()
13919 );
13920 }
13921
13922 #[test]
13923 fn protocol_v2_fetch_exchange_writes_request_and_reads_response() {
13924 let want = ObjectId::from_hex(
13925 ObjectFormat::Sha1,
13926 "1111111111111111111111111111111111111111",
13927 )
13928 .expect("test operation should succeed");
13929 let request = ProtocolV2FetchRequest {
13930 wants: vec![want],
13931 thin_pack: true,
13932 done: true,
13933 ..ProtocolV2FetchRequest::default()
13934 };
13935 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13936 ProtocolV2FetchAcknowledgment::Nak,
13937 ])];
13938 let mut response = Vec::new();
13939 write_protocol_v2_fetch_response(&mut response, §ions)
13940 .expect("test operation should succeed");
13941
13942 let mut input = response.as_slice();
13943 let mut output = Vec::new();
13944 assert_eq!(
13945 exchange_protocol_v2_fetch(ObjectFormat::Sha1, &mut input, &mut output, &request)
13946 .expect("test operation should succeed"),
13947 sections
13948 );
13949 assert!(input.is_empty());
13950 let mut output_read = output.as_slice();
13951 assert_eq!(
13952 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut output_read)
13953 .expect("test operation should succeed"),
13954 request
13955 );
13956 }
13957
13958 #[test]
13959 fn protocol_v2_fetch_packfile_demuxes_sideband_section() {
13960 let sections = vec![
13961 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13962 ProtocolV2FetchAcknowledgment::Nak,
13963 ]),
13964 ProtocolV2FetchResponseSection::Packfile(vec![
13965 b"\x01PACK".to_vec(),
13966 b"\x02counting objects\n".to_vec(),
13967 b"\x01 bytes".to_vec(),
13968 b"\x02done\n".to_vec(),
13969 ]),
13970 ];
13971
13972 assert_eq!(
13973 demux_protocol_v2_fetch_packfile(§ions).expect("test operation should succeed"),
13974 Some(SideBandDemux {
13975 data: b"PACK bytes".to_vec(),
13976 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
13977 })
13978 );
13979 assert_eq!(
13980 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Acknowledgments(
13981 vec![ProtocolV2FetchAcknowledgment::Nak],
13982 )])
13983 .expect("test operation should succeed"),
13984 None
13985 );
13986 }
13987
13988 #[test]
13989 fn protocol_v2_fetch_packfile_demux_rejects_duplicate_or_bad_sideband() {
13990 assert!(
13991 demux_protocol_v2_fetch_packfile(&[
13992 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK".to_vec()]),
13993 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01more".to_vec()]),
13994 ])
13995 .is_err()
13996 );
13997 assert!(
13998 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13999 b"\x03remote died\n".to_vec()
14000 ])])
14001 .is_err()
14002 );
14003 assert!(
14004 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
14005 b"\x04bad".to_vec()
14006 ])])
14007 .is_err()
14008 );
14009 }
14010
14011 #[test]
14012 fn protocol_v2_fetch_response_rejects_malformed_sections() {
14013 assert!(
14014 parse_protocol_v2_fetch_response(
14015 ObjectFormat::Sha1,
14016 &[PktLineFrame::Data(b"acknowledgments\n".to_vec())],
14017 )
14018 .is_err()
14019 );
14020 assert!(
14021 parse_protocol_v2_fetch_response(
14022 ObjectFormat::Sha1,
14023 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
14024 )
14025 .is_err()
14026 );
14027 assert!(
14028 parse_protocol_v2_fetch_response(
14029 ObjectFormat::Sha1,
14030 &[
14031 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
14032 PktLineFrame::Data(b"ACK not-an-oid\n".to_vec()),
14033 PktLineFrame::Flush,
14034 ],
14035 )
14036 .is_err()
14037 );
14038 assert!(
14039 parse_protocol_v2_fetch_response(
14040 ObjectFormat::Sha1,
14041 &[
14042 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
14043 PktLineFrame::Data(b"https://example.invalid/pack-a.pack\n".to_vec()),
14044 PktLineFrame::Flush,
14045 ],
14046 )
14047 .is_err()
14048 );
14049 assert!(
14050 parse_protocol_v2_fetch_response(
14051 ObjectFormat::Sha1,
14052 &[
14053 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
14054 PktLineFrame::Data(
14055 b"not-a-hash https://example.invalid/pack-a.pack\n".to_vec()
14056 ),
14057 PktLineFrame::Flush,
14058 ],
14059 )
14060 .is_err()
14061 );
14062 assert!(
14063 encode_protocol_v2_fetch_response(&[ProtocolV2FetchResponseSection::WantedRefs(vec![
14064 ProtocolV2FetchWantedRef {
14065 oid: ObjectId::from_hex(
14066 ObjectFormat::Sha1,
14067 "1111111111111111111111111111111111111111",
14068 )
14069 .expect("test operation should succeed"),
14070 name: "bad ref".into(),
14071 }
14072 ])])
14073 .is_err()
14074 );
14075 }
14076
14077 #[test]
14078 fn protocol_v2_ls_refs_response_bridges_into_ref_advertisement_set() {
14079 let head = ObjectId::from_hex(
14080 ObjectFormat::Sha1,
14081 "1111111111111111111111111111111111111111",
14082 )
14083 .expect("test operation should succeed");
14084 let tag = ObjectId::from_hex(
14085 ObjectFormat::Sha1,
14086 "2222222222222222222222222222222222222222",
14087 )
14088 .expect("test operation should succeed");
14089 let tag_peeled = ObjectId::from_hex(
14090 ObjectFormat::Sha1,
14091 "3333333333333333333333333333333333333333",
14092 )
14093 .expect("test operation should succeed");
14094 let frames = vec![
14095 PktLineFrame::Data(
14096 b"1111111111111111111111111111111111111111 HEAD symref-target:refs/heads/main\n"
14097 .to_vec(),
14098 ),
14099 PktLineFrame::Data(
14100 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
14101 ),
14102 PktLineFrame::Data(
14103 b"2222222222222222222222222222222222222222 refs/tags/v1 peeled:3333333333333333333333333333333333333333\n"
14104 .to_vec(),
14105 ),
14106 PktLineFrame::Flush,
14107 ];
14108
14109 let set = parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
14110 ObjectFormat::Sha1,
14111 &frames,
14112 )
14113 .expect("test operation should succeed");
14114 assert_eq!(
14115 set,
14116 RefAdvertisementSet {
14117 protocol: ProtocolVersion::V2,
14118 refs: vec![
14119 RefAdvertisement {
14120 oid: head.clone(),
14121 name: "HEAD".into(),
14122 capabilities: vec![Capability {
14123 name: "symref".into(),
14124 value: Some("HEAD:refs/heads/main".into()),
14125 }],
14126 },
14127 RefAdvertisement {
14128 oid: head,
14129 name: "refs/heads/main".into(),
14130 capabilities: Vec::new(),
14131 },
14132 RefAdvertisement {
14133 oid: tag,
14134 name: "refs/tags/v1".into(),
14135 capabilities: Vec::new(),
14136 },
14137 RefAdvertisement {
14138 oid: tag_peeled,
14139 name: "refs/tags/v1^{}".into(),
14140 capabilities: Vec::new(),
14141 },
14142 ],
14143 shallow: Vec::new(),
14144 }
14145 );
14146
14147 let mut encoded = Vec::new();
14149 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
14150 encoded.extend_from_slice(b"tail");
14151 let mut input = encoded.as_slice();
14152 assert_eq!(
14153 read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
14154 ObjectFormat::Sha1,
14155 &mut input,
14156 )
14157 .expect("test operation should succeed"),
14158 set,
14159 );
14160 assert_eq!(input, b"tail");
14161 }
14162
14163 #[test]
14164 fn protocol_v2_ls_refs_records_bridge_unborn_head_symref_and_empty() {
14165 let records = vec![ProtocolV2LsRefsRecord::Unborn {
14168 name: "HEAD".into(),
14169 symref_target: Some("refs/heads/main".into()),
14170 attributes: Vec::new(),
14171 }];
14172 assert!(protocol_v2_ls_refs_records_to_ref_advertisement_set(&records).is_err());
14173
14174 assert_eq!(
14176 protocol_v2_ls_refs_records_to_ref_advertisement_set(&[])
14177 .expect("test operation should succeed"),
14178 RefAdvertisementSet {
14179 protocol: ProtocolVersion::V2,
14180 refs: Vec::new(),
14181 shallow: Vec::new(),
14182 }
14183 );
14184
14185 let main = ObjectId::from_hex(
14188 ObjectFormat::Sha1,
14189 "4444444444444444444444444444444444444444",
14190 )
14191 .expect("test operation should succeed");
14192 let records = vec![
14193 ProtocolV2LsRefsRecord::Unborn {
14194 name: "HEAD".into(),
14195 symref_target: Some("refs/heads/main".into()),
14196 attributes: Vec::new(),
14197 },
14198 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
14199 oid: main.clone(),
14200 name: "refs/heads/main".into(),
14201 peeled: None,
14202 symref_target: None,
14203 attributes: Vec::new(),
14204 }),
14205 ];
14206 let set = protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
14207 .expect("test operation should succeed");
14208 assert_eq!(
14209 set,
14210 RefAdvertisementSet {
14211 protocol: ProtocolVersion::V2,
14212 refs: vec![RefAdvertisement {
14213 oid: main,
14214 name: "refs/heads/main".into(),
14215 capabilities: vec![Capability {
14216 name: "symref".into(),
14217 value: Some("HEAD:refs/heads/main".into()),
14218 }],
14219 }],
14220 shallow: Vec::new(),
14221 }
14222 );
14223 }
14224}