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!("packet: {:>12}{} ", packet_trace_prefix(), if is_write {
97 '>'
98 } else {
99 '<'
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
116fn packet_trace_frame(frame: &PktLineFrame, is_write: bool) {
119 if !packet_trace_enabled() {
120 return;
121 }
122 match frame {
123 PktLineFrame::Data(payload) => packet_trace(payload, is_write),
124 PktLineFrame::Flush => packet_trace(b"0000", is_write),
125 PktLineFrame::Delimiter => packet_trace(b"0001", is_write),
126 PktLineFrame::ResponseEnd => packet_trace(b"0002", is_write),
127 }
128}
129
130pub const PKT_LINE_MAX_LEN: usize = 65_520;
131
132pub const PKT_LINE_MAX_PAYLOAD_LEN: usize = PKT_LINE_MAX_LEN - 4;
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub enum ProtocolVersion {
136 V0,
137 V1,
138 V2,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct PktLine(pub Vec<u8>);
143
144impl PktLine {
145 pub fn encode(&self) -> Vec<u8> {
146 encode_pkt_line_payload(&self.0)
147 }
148
149 pub fn try_encode(&self) -> Result<Vec<u8>> {
150 validate_pkt_line_payload(&self.0)?;
151 Ok(self.encode())
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum PktLineFrame {
157 Data(Vec<u8>),
158 Flush,
159 Delimiter,
160 ResponseEnd,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct ProtocolErrorLine {
165 pub message: String,
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum GitService {
170 UploadPack,
171 ReceivePack,
172 UploadArchive,
173}
174
175impl GitService {
176 pub fn as_str(self) -> &'static str {
177 match self {
178 Self::UploadPack => "git-upload-pack",
179 Self::ReceivePack => "git-receive-pack",
180 Self::UploadArchive => "git-upload-archive",
181 }
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct RefSpec {
187 pub force: bool,
188 pub negative: bool,
189 pub src: Option<String>,
190 pub dst: Option<String>,
191 pub pattern: bool,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct FetchHeadRecord {
196 pub oid: ObjectId,
197 pub not_for_merge: bool,
198 pub description: String,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct FetchRefUpdate {
203 pub src: String,
204 pub dst: Option<String>,
205 pub oid: ObjectId,
206 pub not_for_merge: bool,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct PushSourceRef {
211 pub name: String,
212 pub oid: ObjectId,
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub enum SideBandChannel {
217 Data,
218 Progress,
219 Fatal,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct SideBandPacket {
224 pub channel: SideBandChannel,
225 pub data: Vec<u8>,
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Default)]
229pub struct SideBandDemux {
230 pub data: Vec<u8>,
231 pub progress: Vec<Vec<u8>>,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Default)]
235pub struct UploadArchiveRequest {
236 pub arguments: Vec<String>,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub enum UploadArchiveResponse {
241 Ack { sideband: Vec<SideBandPacket> },
242 Nack { message: String },
243}
244
245impl PktLineFrame {
246 pub fn data(payload: impl Into<Vec<u8>>) -> Result<Self> {
247 let payload = payload.into();
248 validate_pkt_line_payload(&payload)?;
249 Ok(Self::Data(payload))
250 }
251
252 pub fn encode(&self) -> Vec<u8> {
253 match self {
254 Self::Data(payload) => encode_pkt_line_payload(payload),
255 Self::Flush => b"0000".to_vec(),
256 Self::Delimiter => b"0001".to_vec(),
257 Self::ResponseEnd => b"0002".to_vec(),
258 }
259 }
260
261 pub fn try_encode(&self) -> Result<Vec<u8>> {
262 match self {
263 Self::Data(payload) => try_encode_pkt_line_payload(payload),
264 Self::Flush | Self::Delimiter | Self::ResponseEnd => Ok(self.encode()),
265 }
266 }
267
268 pub fn parse(input: &[u8]) -> Result<(Self, usize)> {
269 if input.len() < 4 {
270 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
271 }
272 let len = parse_pkt_len(&input[..4])?;
273 match len {
274 0 => Ok((Self::Flush, 4)),
275 1 => Ok((Self::Delimiter, 4)),
276 2 => Ok((Self::ResponseEnd, 4)),
277 3 => Err(GitError::InvalidFormat(
278 "reserved pkt-line length 0003".into(),
279 )),
280 4..=PKT_LINE_MAX_LEN => {
281 if input.len() < len {
282 return Err(GitError::InvalidFormat(format!(
283 "truncated pkt-line payload: expected {} bytes, got {}",
284 len - 4,
285 input.len().saturating_sub(4)
286 )));
287 }
288 Ok((Self::Data(input[4..len].to_vec()), len))
289 }
290 _ => Err(GitError::InvalidFormat(format!(
291 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
292 ))),
293 }
294 }
295}
296
297fn validate_pkt_line_payload(payload: &[u8]) -> Result<()> {
298 if payload.len() > PKT_LINE_MAX_PAYLOAD_LEN {
299 return Err(GitError::InvalidFormat(format!(
300 "pkt-line payload exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
301 )));
302 }
303 Ok(())
304}
305
306fn pkt_line_header(len: usize) -> [u8; 4] {
307 const HEX: &[u8; 16] = b"0123456789abcdef";
308 [
309 HEX[(len >> 12) & 0xf],
310 HEX[(len >> 8) & 0xf],
311 HEX[(len >> 4) & 0xf],
312 HEX[len & 0xf],
313 ]
314}
315
316fn encode_pkt_line_payload(payload: &[u8]) -> Vec<u8> {
317 let len = payload.len() + 4;
318 let mut out = Vec::with_capacity(len);
319 out.extend_from_slice(&pkt_line_header(len));
320 out.extend_from_slice(payload);
321 out
322}
323
324fn try_encode_pkt_line_payload(payload: &[u8]) -> Result<Vec<u8>> {
325 validate_pkt_line_payload(payload)?;
326 Ok(encode_pkt_line_payload(payload))
327}
328
329pub fn parse_pkt_line_stream(mut input: &[u8]) -> Result<Vec<PktLineFrame>> {
330 let mut frames = Vec::new();
331 while !input.is_empty() {
332 let (frame, consumed) = PktLineFrame::parse(input)?;
333 frames.push(frame);
334 input = &input[consumed..];
335 }
336 Ok(frames)
337}
338
339fn parse_pkt_line_frames_until_flush_from(mut input: &[u8]) -> Result<(Vec<PktLineFrame>, usize)> {
340 let mut frames = Vec::new();
341 let mut total = 0usize;
342 loop {
343 if input.is_empty() {
344 return Err(GitError::InvalidFormat(
345 "pkt-line stream ended before flush".into(),
346 ));
347 }
348 let (frame, consumed) = PktLineFrame::parse(input)?;
349 total += consumed;
350 let done = matches!(frame, PktLineFrame::Flush);
351 frames.push(frame);
352 input = &input[consumed..];
353 if done {
354 return Ok((frames, total));
355 }
356 }
357}
358
359pub fn read_pkt_line_frame(reader: &mut impl Read) -> Result<Option<PktLineFrame>> {
360 let mut header = [0u8; 4];
361 let mut read = 0usize;
362 while read < header.len() {
363 match reader.read(&mut header[read..]) {
364 Ok(0) if read == 0 => return Ok(None),
365 Ok(0) => {
366 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
367 }
368 Ok(n) => read += n,
369 Err(err) if err.kind() == ErrorKind::Interrupted => {}
370 Err(err) => return Err(err.into()),
371 }
372 }
373
374 let len = parse_pkt_len(&header)?;
375 let frame = match len {
376 0 => PktLineFrame::Flush,
377 1 => PktLineFrame::Delimiter,
378 2 => PktLineFrame::ResponseEnd,
379 3 => {
380 return Err(GitError::InvalidFormat(
381 "reserved pkt-line length 0003".into(),
382 ));
383 }
384 4..=PKT_LINE_MAX_LEN => {
385 let mut payload = vec![0; len - 4];
386 reader.read_exact(&mut payload)?;
387 PktLineFrame::Data(payload)
388 }
389 _ => {
390 return Err(GitError::InvalidFormat(format!(
391 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
392 )));
393 }
394 };
395 packet_trace_frame(&frame, false);
396 Ok(Some(frame))
397}
398
399pub fn read_pkt_line_frames(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
400 let mut frames = Vec::new();
401 while let Some(frame) = read_pkt_line_frame(reader)? {
402 frames.push(frame);
403 }
404 Ok(frames)
405}
406
407pub fn read_pkt_line_frames_until_flush(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
408 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::Flush))
409}
410
411pub fn read_pkt_line_frames_until_response_end(
412 reader: &mut impl Read,
413) -> Result<Vec<PktLineFrame>> {
414 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::ResponseEnd))
415}
416
417fn read_pkt_line_frames_until_control(
418 reader: &mut impl Read,
419 stop: impl Fn(&PktLineFrame) -> bool,
420) -> Result<Vec<PktLineFrame>> {
421 let mut frames = Vec::new();
422 loop {
423 let Some(frame) = read_pkt_line_frame(reader)? else {
424 return Err(GitError::InvalidFormat(
425 "pkt-line stream ended before control packet".into(),
426 ));
427 };
428 let done = stop(&frame);
429 frames.push(frame);
430 if done {
431 return Ok(frames);
432 }
433 }
434}
435
436pub fn write_pkt_line_frame(writer: &mut impl Write, frame: &PktLineFrame) -> Result<()> {
437 match frame {
438 PktLineFrame::Data(payload) => write_pkt_line_payload(writer, payload)?,
440 PktLineFrame::Flush => {
441 packet_trace(b"0000", true);
442 writer.write_all(b"0000")?;
443 }
444 PktLineFrame::Delimiter => {
445 packet_trace(b"0001", true);
446 writer.write_all(b"0001")?;
447 }
448 PktLineFrame::ResponseEnd => {
449 packet_trace(b"0002", true);
450 writer.write_all(b"0002")?;
451 }
452 }
453 Ok(())
454}
455
456pub fn write_pkt_line_payload(writer: &mut impl Write, payload: &[u8]) -> Result<()> {
457 validate_pkt_line_payload(payload)?;
458 packet_trace(payload, true);
459 let len = payload.len() + 4;
460 writer.write_all(&pkt_line_header(len))?;
461 writer.write_all(payload)?;
462 Ok(())
463}
464
465pub fn write_pkt_line_frames(writer: &mut impl Write, frames: &[PktLineFrame]) -> Result<()> {
466 for frame in frames {
467 write_pkt_line_frame(writer, frame)?;
468 }
469 Ok(())
470}
471
472pub fn parse_error_line(payload: &[u8]) -> Result<ProtocolErrorLine> {
473 let text = parse_protocol_v2_line_text("protocol error line", payload)?;
474 let Some(message) = text.strip_prefix("ERR ") else {
475 return Err(GitError::InvalidFormat(
476 "protocol error line must start with ERR".into(),
477 ));
478 };
479 validate_protocol_error_message(message)?;
480 Ok(ProtocolErrorLine {
481 message: message.to_string(),
482 })
483}
484
485pub fn encode_error_line(error: &ProtocolErrorLine) -> Result<Vec<u8>> {
486 validate_protocol_error_message(&error.message)?;
487 Ok(line_from_str(&format!("ERR {}", error.message)))
488}
489
490pub fn parse_error_frame(frame: &PktLineFrame) -> Result<Option<ProtocolErrorLine>> {
491 match frame {
492 PktLineFrame::Data(payload) if trim_trailing_lf(payload).starts_with(b"ERR ") => {
493 parse_error_line(payload).map(Some)
494 }
495 PktLineFrame::Data(_)
496 | PktLineFrame::Flush
497 | PktLineFrame::Delimiter
498 | PktLineFrame::ResponseEnd => Ok(None),
499 }
500}
501
502pub fn read_error_line(reader: &mut impl Read) -> Result<ProtocolErrorLine> {
503 let Some(frame) = read_pkt_line_frame(reader)? else {
504 return Err(GitError::InvalidFormat(
505 "pkt-line stream ended before protocol error line".into(),
506 ));
507 };
508 match frame {
509 PktLineFrame::Data(payload) => parse_error_line(&payload),
510 _ => Err(GitError::InvalidFormat(
511 "protocol error line must be a data packet".into(),
512 )),
513 }
514}
515
516pub fn write_error_line(writer: &mut impl Write, error: &ProtocolErrorLine) -> Result<()> {
517 write_pkt_line_frame(writer, &PktLineFrame::data(encode_error_line(error)?)?)
518}
519
520pub fn parse_git_service(value: &str) -> Result<GitService> {
521 match value {
522 "git-upload-pack" => Ok(GitService::UploadPack),
523 "git-receive-pack" => Ok(GitService::ReceivePack),
524 "git-upload-archive" => Ok(GitService::UploadArchive),
525 other => Err(GitError::InvalidFormat(format!(
526 "unsupported git service {other}"
527 ))),
528 }
529}
530
531pub fn parse_refspec(value: &str) -> Result<RefSpec> {
532 validate_refspec_value(value)?;
533 let (force, value) = value
534 .strip_prefix('+')
535 .map_or((false, value), |value| (true, value));
536 let (negative, value) = value
537 .strip_prefix('^')
538 .map_or((false, value), |value| (true, value));
539 if force && negative {
540 return Err(GitError::InvalidFormat(
541 "negative refspec must not be forced".into(),
542 ));
543 }
544 let (src, dst) = if negative {
545 if value.contains(':') {
546 return Err(GitError::InvalidFormat(
547 "negative refspec must not have a destination".into(),
548 ));
549 }
550 (Some(value), None)
551 } else if let Some((src, dst)) = value.split_once(':') {
552 (non_empty(src), non_empty(dst))
553 } else {
554 (Some(value), None)
555 };
556 if src.is_none() && dst.is_none() && value != ":" {
557 return Err(GitError::InvalidFormat(
558 "refspec must include a source or destination".into(),
559 ));
560 }
561 if negative && src.is_none() {
562 return Err(GitError::InvalidFormat(
563 "negative refspec is missing a source".into(),
564 ));
565 }
566 if let Some(src) = src {
567 validate_refspec_endpoint("refspec source", src)?;
568 }
569 if let Some(dst) = dst {
570 validate_refspec_endpoint("refspec destination", dst)?;
571 }
572 let src_pattern_count = src.map(count_refspec_wildcards).unwrap_or(0);
573 let dst_pattern_count = dst.map(count_refspec_wildcards).unwrap_or(0);
574 if src_pattern_count > 1 || dst_pattern_count > 1 {
575 return Err(GitError::InvalidFormat(
576 "refspec endpoint has too many wildcards".into(),
577 ));
578 }
579 if dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
580 return Err(GitError::InvalidFormat(
581 "refspec wildcard must appear in both source and destination".into(),
582 ));
583 }
584 Ok(RefSpec {
585 force,
586 negative,
587 src: src.map(str::to_string),
588 dst: dst.map(str::to_string),
589 pattern: src_pattern_count == 1 || dst_pattern_count == 1,
590 })
591}
592
593pub fn encode_refspec(refspec: &RefSpec) -> Result<String> {
594 validate_refspec_shape(refspec)?;
595 let mut out = String::new();
596 if refspec.force {
597 out.push('+');
598 }
599 if refspec.negative {
600 out.push('^');
601 }
602 if let Some(src) = &refspec.src {
603 out.push_str(src);
604 }
605 if !refspec.negative && refspec.src.is_none() && refspec.dst.is_none() {
606 out.push(':');
607 } else if !refspec.negative && refspec.dst.is_some() {
608 out.push(':');
609 if let Some(dst) = &refspec.dst {
610 out.push_str(dst);
611 }
612 }
613 Ok(out)
614}
615
616pub fn refspec_matches_source(refspec: &RefSpec, source: &str) -> Result<bool> {
617 Ok(refspec_map_source(refspec, source)?.is_some())
618}
619
620pub fn refspec_map_source(refspec: &RefSpec, source: &str) -> Result<Option<String>> {
621 validate_refspec_shape(refspec)?;
622 validate_refspec_endpoint("refspec match source", source)?;
623 let Some(src) = refspec.src.as_deref() else {
624 return Ok(None);
625 };
626 if refspec.pattern {
627 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
628 return Ok(None);
629 };
630 let Some(middle) = source
631 .strip_prefix(src_prefix)
632 .and_then(|value| value.strip_suffix(src_suffix))
633 else {
634 return Ok(None);
635 };
636 if let Some(dst) = refspec.dst.as_deref() {
637 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
638 GitError::InvalidFormat("pattern refspec destination is missing wildcard".into())
639 })?;
640 return Ok(Some(format!("{dst_prefix}{middle}{dst_suffix}")));
641 }
642 return Ok(Some(source.to_string()));
643 }
644 if src == source {
645 return Ok(Some(
646 refspec.dst.clone().unwrap_or_else(|| source.to_string()),
647 ));
648 }
649 Ok(None)
650}
651
652pub fn fetch_head_ref_description(refname: &str) -> Result<String> {
653 validate_fetch_head_description_field(refname)?;
654 if refname == "HEAD" {
658 Ok(String::new())
659 } else if let Some(branch) = refname.strip_prefix("refs/heads/") {
660 Ok(format!("branch '{branch}'"))
661 } else if let Some(tag) = refname.strip_prefix("refs/tags/") {
662 Ok(format!("tag '{tag}'"))
663 } else if let Some(rest) = refname.strip_prefix("refs/remotes/") {
664 Ok(format!("remote-tracking branch '{rest}'"))
665 } else {
666 Ok(format!("'{refname}'"))
667 }
668}
669
670pub fn fetch_head_remote_description(refname: &str, remote: &str) -> Result<String> {
671 validate_fetch_head_description_field(remote)?;
672 let what = fetch_head_ref_description(refname)?;
675 if what.is_empty() {
676 Ok(remote.to_string())
677 } else {
678 Ok(format!("{what} of {remote}"))
679 }
680}
681
682pub fn parse_fetch_head(format: ObjectFormat, input: &[u8]) -> Result<Vec<FetchHeadRecord>> {
683 if input.is_empty() {
684 return Ok(Vec::new());
685 }
686 input
687 .split_inclusive(|byte| *byte == b'\n')
688 .map(|line| parse_fetch_head_record(format, line))
689 .collect()
690}
691
692pub fn encode_fetch_head(records: &[FetchHeadRecord]) -> Result<Vec<u8>> {
693 let mut out = Vec::new();
694 for record in records {
695 validate_fetch_head_description_field(&record.description)?;
696 out.extend_from_slice(record.oid.to_string().as_bytes());
697 out.push(b'\t');
698 if record.not_for_merge {
699 out.extend_from_slice(b"not-for-merge");
700 }
701 out.push(b'\t');
702 out.extend_from_slice(record.description.as_bytes());
703 out.push(b'\n');
704 }
705 Ok(out)
706}
707
708pub fn read_fetch_head(
709 format: ObjectFormat,
710 reader: &mut impl Read,
711) -> Result<Vec<FetchHeadRecord>> {
712 let mut input = Vec::new();
713 reader.read_to_end(&mut input)?;
714 parse_fetch_head(format, &input)
715}
716
717pub fn write_fetch_head(writer: &mut impl Write, records: &[FetchHeadRecord]) -> Result<()> {
718 for record in records {
719 validate_fetch_head_description_field(&record.description)?;
720 writer.write_all(record.oid.to_string().as_bytes())?;
721 writer.write_all(b"\t")?;
722 if record.not_for_merge {
723 writer.write_all(b"not-for-merge")?;
724 }
725 writer.write_all(b"\t")?;
726 writer.write_all(record.description.as_bytes())?;
727 writer.write_all(b"\n")?;
728 }
729 Ok(())
730}
731
732fn find_advertised_ref_by_name_abbrev<'a>(
738 refs: &'a [RefAdvertisement],
739 name: &str,
740) -> Option<&'a RefAdvertisement> {
741 let mut best: Option<(&RefAdvertisement, usize)> = None;
742 for reference in refs {
743 let score = fetch_refname_match_score(name, &reference.name);
744 if score > best.map(|(_, score)| score).unwrap_or(0) {
745 best = Some((reference, score));
746 }
747 }
748 best.map(|(reference, _)| reference)
749}
750
751fn fetch_refname_match_score(abbrev: &str, full: &str) -> usize {
754 let expansions = [
755 abbrev.to_string(),
756 format!("refs/{abbrev}"),
757 format!("refs/tags/{abbrev}"),
758 format!("refs/heads/{abbrev}"),
759 format!("refs/remotes/{abbrev}"),
760 format!("refs/remotes/{abbrev}/HEAD"),
761 ];
762 for (index, candidate) in expansions.iter().enumerate() {
763 if candidate == full {
764 return expansions.len() - index;
765 }
766 }
767 0
768}
769
770pub fn refname_matches(abbrev: &str, full: &str) -> bool {
775 fetch_refname_match_score(abbrev, full) > 0
776}
777
778fn fetch_local_ref_name(name: &str) -> String {
782 if name.starts_with("refs/") {
783 name.to_string()
784 } else if name.starts_with("heads/")
785 || name.starts_with("tags/")
786 || name.starts_with("remotes/")
787 {
788 format!("refs/{name}")
789 } else {
790 format!("refs/heads/{name}")
791 }
792}
793
794pub fn plan_fetch_ref_updates(
795 refs: &[RefAdvertisement],
796 refspecs: &[RefSpec],
797 auto_follow_tags: bool,
798) -> Result<Vec<FetchRefUpdate>> {
799 let negative = refspecs
800 .iter()
801 .filter(|refspec| refspec.negative)
802 .collect::<Vec<_>>();
803 let mut updates = Vec::new();
804 for refspec in refspecs.iter().filter(|refspec| !refspec.negative) {
805 validate_refspec_shape(refspec)?;
806 let Some(src) = refspec.src.as_deref() else {
807 return Err(GitError::InvalidFormat(
808 "fetch refspec is missing a source".into(),
809 ));
810 };
811 if refspec.pattern {
812 for reference in refs {
813 if refspec_is_excluded(&negative, &reference.name)? {
814 continue;
815 }
816 if let Some(dst) = refspec_map_source(refspec, &reference.name)? {
817 updates.push(FetchRefUpdate {
818 src: reference.name.clone(),
819 dst: Some(dst),
820 oid: reference.oid,
821 not_for_merge: false,
822 });
823 }
824 }
825 continue;
826 }
827 if refspec_is_excluded(&negative, src)? {
828 continue;
829 }
830 let Some(reference) = find_advertised_ref_by_name_abbrev(refs, src) else {
831 return Err(GitError::reference_not_found(format!("remote ref {src}")));
832 };
833 updates.push(FetchRefUpdate {
834 src: reference.name.clone(),
835 dst: refspec.dst.as_deref().map(fetch_local_ref_name),
836 oid: reference.oid,
837 not_for_merge: false,
838 });
839 }
840 if auto_follow_tags && updates.iter().any(|update| update.dst.is_some()) {
841 let fetched_oids = updates.iter().map(|update| update.oid).collect::<Vec<_>>();
842 let fetched_srcs = updates
843 .iter()
844 .map(|update| update.src.clone())
845 .collect::<Vec<_>>();
846 for reference in refs {
847 if reference.name.starts_with("refs/tags/")
848 && fetched_oids.iter().any(|oid| oid == &reference.oid)
849 && !fetched_srcs.contains(&reference.name)
850 && !refspec_is_excluded(&negative, &reference.name)?
851 {
852 updates.push(FetchRefUpdate {
853 src: reference.name.clone(),
854 dst: Some(reference.name.clone()),
855 oid: reference.oid,
856 not_for_merge: true,
857 });
858 }
859 }
860 }
861 Ok(updates)
862}
863
864pub fn fetch_ref_updates_to_fetch_head(
865 updates: &[FetchRefUpdate],
866 remote: &str,
867) -> Result<Vec<FetchHeadRecord>> {
868 updates
869 .iter()
870 .map(|update| {
871 Ok(FetchHeadRecord {
872 oid: update.oid,
873 not_for_merge: update.not_for_merge,
874 description: fetch_head_remote_description(&update.src, remote)?,
875 })
876 })
877 .collect()
878}
879
880pub fn plan_push_commands(
881 format: ObjectFormat,
882 local_refs: &[PushSourceRef],
883 remote_refs: &[RefAdvertisement],
884 refspecs: &[RefSpec],
885) -> Result<Vec<ReceivePackCommand>> {
886 let zero = zero_object_id(format)?;
887 let mut commands = Vec::new();
888 for refspec in refspecs {
889 validate_refspec_shape(refspec)?;
890 if refspec.negative {
891 return Err(GitError::InvalidFormat(
892 "push refspec must not be negative".into(),
893 ));
894 }
895 match (refspec.src.as_deref(), refspec.dst.as_deref()) {
896 (None, None) => {
897 for local in local_refs {
903 if !local.name.starts_with("refs/") {
904 continue;
905 }
906 validate_push_source_ref(format, local)?;
907 if let Some(remote) = remote_ref(remote_refs, &local.name) {
908 commands.push(ReceivePackCommand {
909 old_id: remote.oid,
910 new_id: local.oid,
911 name: local.name.clone(),
912 });
913 }
914 }
915 }
916 (None, Some(dst)) => {
917 validate_refspec_endpoint("push destination", dst)?;
918 let remote = remote_ref(remote_refs, dst)
919 .ok_or_else(|| GitError::reference_not_found(format!("remote ref {dst}")))?;
920 commands.push(ReceivePackCommand {
921 old_id: remote.oid,
922 new_id: zero.clone(),
923 name: dst.to_string(),
924 });
925 }
926 (Some(src), dst) if refspec.pattern => {
927 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
928 return Err(GitError::InvalidFormat(
929 "pattern push refspec source is missing wildcard".into(),
930 ));
931 };
932 let dst = dst.ok_or_else(|| {
933 GitError::InvalidFormat("pattern push refspec is missing destination".into())
934 })?;
935 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
936 GitError::InvalidFormat(
937 "pattern push refspec destination is missing wildcard".into(),
938 )
939 })?;
940 for local in local_refs {
941 validate_push_source_ref(format, local)?;
942 let Some(middle) = local
943 .name
944 .strip_prefix(src_prefix)
945 .and_then(|value| value.strip_suffix(src_suffix))
946 else {
947 continue;
948 };
949 let name = format!("{dst_prefix}{middle}{dst_suffix}");
950 let old_id = remote_ref(remote_refs, &name)
951 .map(|reference| reference.oid)
952 .unwrap_or_else(|| zero.clone());
953 commands.push(ReceivePackCommand {
954 old_id,
955 new_id: local.oid,
956 name,
957 });
958 }
959 }
960 (Some(src), dst) => {
961 validate_refspec_endpoint("push source", src)?;
962 let local = local_ref(local_refs, src)
963 .ok_or_else(|| GitError::reference_not_found(format!("local ref {src}")))?;
964 validate_push_source_ref(format, local)?;
965 let name = dst.unwrap_or(src);
966 validate_refspec_endpoint("push destination", name)?;
967 let old_id = remote_ref(remote_refs, name)
968 .map(|reference| reference.oid)
969 .unwrap_or_else(|| zero.clone());
970 commands.push(ReceivePackCommand {
971 old_id,
972 new_id: local.oid,
973 name: name.to_string(),
974 });
975 }
976 }
977 }
978 Ok(commands)
979}
980
981pub fn build_receive_pack_push_request(
982 features: &ReceivePackFeatures,
983 commands: Vec<ReceivePackCommand>,
984 packfile: Vec<u8>,
985 options: ReceivePackPushRequestOptions,
986) -> Result<ReceivePackPushRequest> {
987 let mut capabilities = Vec::new();
988 if options.report_status_v2 {
989 require_receive_pack_feature(features.report_status_v2, "report-status-v2")?;
990 capabilities.push(Capability {
991 name: "report-status-v2".into(),
992 value: None,
993 });
994 } else if options.report_status {
995 require_receive_pack_feature(features.report_status, "report-status")?;
996 capabilities.push(Capability {
997 name: "report-status".into(),
998 value: None,
999 });
1000 }
1001 if commands.iter().any(is_receive_pack_delete_command) {
1002 require_receive_pack_feature(features.delete_refs, "delete-refs")?;
1003 capabilities.push(Capability {
1004 name: "delete-refs".into(),
1005 value: None,
1006 });
1007 }
1008 if options.atomic {
1009 require_receive_pack_feature(features.atomic, "atomic")?;
1010 capabilities.push(Capability {
1011 name: "atomic".into(),
1012 value: None,
1013 });
1014 }
1015 if options.ofs_delta {
1016 require_receive_pack_feature(features.ofs_delta, "ofs-delta")?;
1017 capabilities.push(Capability {
1018 name: "ofs-delta".into(),
1019 value: None,
1020 });
1021 }
1022 if options.side_band_64k {
1023 require_receive_pack_feature(features.side_band_64k, "side-band-64k")?;
1024 capabilities.push(Capability {
1025 name: "side-band-64k".into(),
1026 value: None,
1027 });
1028 }
1029 if options.quiet {
1030 require_receive_pack_feature(features.quiet, "quiet")?;
1031 capabilities.push(Capability {
1032 name: "quiet".into(),
1033 value: None,
1034 });
1035 }
1036 if let Some(agent) = &options.agent {
1037 validate_capability_field("receive-pack request agent", agent)?;
1038 capabilities.push(Capability {
1039 name: "agent".into(),
1040 value: Some(agent.clone()),
1041 });
1042 }
1043 if let Some(format) = options.object_format {
1044 if features.object_format != Some(format) {
1045 return Err(GitError::InvalidFormat(
1046 "receive-pack request object-format was not advertised".into(),
1047 ));
1048 }
1049 capabilities.push(Capability {
1050 name: "object-format".into(),
1051 value: Some(format.name().into()),
1052 });
1053 }
1054 let push_options = if options.push_options.is_empty() {
1055 None
1056 } else {
1057 require_receive_pack_feature(features.push_options, "push-options")?;
1058 for option in &options.push_options {
1059 validate_receive_pack_push_option(option.as_bytes())?;
1060 }
1061 capabilities.push(Capability {
1062 name: "push-options".into(),
1063 value: None,
1064 });
1065 Some(options.push_options)
1066 };
1067 let request = ReceivePackPushRequest {
1068 commands: ReceivePackRequest {
1069 commands,
1070 capabilities,
1071 shallow: Vec::new(),
1072 },
1073 push_options,
1074 packfile,
1075 };
1076 validate_receive_pack_push_request_features(features, &request)?;
1077 Ok(request)
1078}
1079
1080pub fn smart_http_info_refs_path(repository_path: &str, service: GitService) -> Result<String> {
1081 validate_smart_http_service(service)?;
1082 let repository_path = normalize_http_repository_path(repository_path)?;
1083 Ok(format!(
1084 "{repository_path}/info/refs?service={}",
1085 service.as_str()
1086 ))
1087}
1088
1089pub fn smart_http_rpc_path(repository_path: &str, service: GitService) -> Result<String> {
1090 validate_smart_http_service(service)?;
1091 let repository_path = normalize_http_repository_path(repository_path)?;
1092 Ok(format!("{repository_path}/{}", service.as_str()))
1093}
1094
1095pub fn dumb_http_info_refs_path(repository_path: &str) -> Result<String> {
1096 let repository_path = normalize_http_repository_path(repository_path)?;
1097 Ok(format!("{repository_path}/info/refs"))
1098}
1099
1100pub fn dumb_http_alternates_path(repository_path: &str) -> Result<String> {
1101 let repository_path = normalize_http_repository_path(repository_path)?;
1102 Ok(format!("{repository_path}/objects/info/http-alternates"))
1103}
1104
1105pub fn dumb_http_packs_path(repository_path: &str) -> Result<String> {
1106 let repository_path = normalize_http_repository_path(repository_path)?;
1107 Ok(format!("{repository_path}/objects/info/packs"))
1108}
1109
1110pub fn dumb_http_loose_object_path(repository_path: &str, oid: &ObjectId) -> Result<String> {
1111 let repository_path = normalize_http_repository_path(repository_path)?;
1112 let oid = oid.to_string();
1113 let (directory, file) = oid.split_at(2);
1114 Ok(format!("{repository_path}/objects/{directory}/{file}"))
1115}
1116
1117pub fn dumb_http_pack_file_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
1118 dumb_http_pack_resource_path(repository_path, hash, "pack")
1119}
1120
1121pub fn dumb_http_pack_index_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
1122 dumb_http_pack_resource_path(repository_path, hash, "idx")
1123}
1124
1125pub fn smart_http_advertisement_content_type(service: GitService) -> Result<String> {
1126 validate_smart_http_service(service)?;
1127 Ok(format!("application/x-{}-advertisement", service.as_str()))
1128}
1129
1130pub fn smart_http_rpc_request_content_type(service: GitService) -> Result<String> {
1131 validate_smart_http_service(service)?;
1132 Ok(format!("application/x-{}-request", service.as_str()))
1133}
1134
1135pub fn smart_http_rpc_result_content_type(service: GitService) -> Result<String> {
1136 validate_smart_http_service(service)?;
1137 Ok(format!("application/x-{}-result", service.as_str()))
1138}
1139
1140pub fn parse_smart_http_advertisement_content_type(value: &str) -> Result<GitService> {
1141 parse_smart_http_content_type(value, "-advertisement")
1142}
1143
1144pub fn parse_smart_http_rpc_request_content_type(value: &str) -> Result<GitService> {
1145 parse_smart_http_content_type(value, "-request")
1146}
1147
1148pub fn parse_smart_http_rpc_result_content_type(value: &str) -> Result<GitService> {
1149 parse_smart_http_content_type(value, "-result")
1150}
1151
1152pub fn parse_sideband_packet(payload: &[u8]) -> Result<SideBandPacket> {
1153 let Some((&channel, data)) = payload.split_first() else {
1154 return Err(GitError::InvalidFormat("sideband packet is empty".into()));
1155 };
1156 let channel = match channel {
1157 1 => SideBandChannel::Data,
1158 2 => SideBandChannel::Progress,
1159 3 => SideBandChannel::Fatal,
1160 other => {
1161 return Err(GitError::InvalidFormat(format!(
1162 "invalid sideband channel {other}"
1163 )));
1164 }
1165 };
1166 Ok(SideBandPacket {
1167 channel,
1168 data: data.to_vec(),
1169 })
1170}
1171
1172pub fn encode_sideband_packet(packet: &SideBandPacket) -> Result<Vec<u8>> {
1173 let mut out = Vec::with_capacity(packet.data.len() + 1);
1174 out.push(match packet.channel {
1175 SideBandChannel::Data => 1,
1176 SideBandChannel::Progress => 2,
1177 SideBandChannel::Fatal => 3,
1178 });
1179 out.extend_from_slice(&packet.data);
1180 if out.len() > PKT_LINE_MAX_PAYLOAD_LEN {
1181 return Err(GitError::InvalidFormat(format!(
1182 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1183 )));
1184 }
1185 Ok(out)
1186}
1187
1188pub fn write_sideband_packet(writer: &mut impl Write, packet: &SideBandPacket) -> Result<()> {
1189 write_sideband_payload(writer, packet.channel, &packet.data)
1190}
1191
1192fn write_sideband_payload(
1193 writer: &mut impl Write,
1194 channel: SideBandChannel,
1195 data: &[u8],
1196) -> Result<()> {
1197 let payload_len = data
1198 .len()
1199 .checked_add(1)
1200 .ok_or_else(|| GitError::InvalidFormat("sideband packet length overflow".into()))?;
1201 if payload_len > PKT_LINE_MAX_PAYLOAD_LEN {
1202 return Err(GitError::InvalidFormat(format!(
1203 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1204 )));
1205 }
1206 writer.write_all(&pkt_line_header(payload_len + 4))?;
1207 writer.write_all(&[match channel {
1208 SideBandChannel::Data => 1,
1209 SideBandChannel::Progress => 2,
1210 SideBandChannel::Fatal => 3,
1211 }])?;
1212 writer.write_all(data)?;
1213 Ok(())
1214}
1215
1216pub fn parse_sideband_packets(payloads: &[Vec<u8>]) -> Result<Vec<SideBandPacket>> {
1217 payloads
1218 .iter()
1219 .map(|payload| parse_sideband_packet(payload))
1220 .collect()
1221}
1222
1223pub fn encode_sideband_packets(packets: &[SideBandPacket]) -> Result<Vec<Vec<u8>>> {
1224 packets.iter().map(encode_sideband_packet).collect()
1225}
1226
1227pub fn parse_sideband_stream(frames: &[PktLineFrame]) -> Result<Vec<SideBandPacket>> {
1228 let mut packets = Vec::new();
1229 let mut saw_flush = false;
1230 for (idx, frame) in frames.iter().enumerate() {
1231 match frame {
1232 PktLineFrame::Data(payload) if !saw_flush => {
1233 packets.push(parse_sideband_packet(payload)?);
1234 }
1235 PktLineFrame::Data(_) => {
1236 return Err(GitError::InvalidFormat(
1237 "sideband stream has data after flush".into(),
1238 ));
1239 }
1240 PktLineFrame::Flush => {
1241 saw_flush = true;
1242 if idx + 1 != frames.len() {
1243 return Err(GitError::InvalidFormat(
1244 "sideband stream has frames after flush".into(),
1245 ));
1246 }
1247 }
1248 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1249 return Err(GitError::InvalidFormat(
1250 "sideband stream contains a non-flush control packet".into(),
1251 ));
1252 }
1253 }
1254 }
1255 if !saw_flush {
1256 return Err(GitError::InvalidFormat(
1257 "sideband stream missing flush".into(),
1258 ));
1259 }
1260 Ok(packets)
1261}
1262
1263pub fn encode_sideband_stream(packets: &[SideBandPacket]) -> Result<Vec<PktLineFrame>> {
1264 let mut frames = Vec::new();
1265 for packet in packets {
1266 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
1267 }
1268 frames.push(PktLineFrame::Flush);
1269 Ok(frames)
1270}
1271
1272pub fn read_sideband_stream(reader: &mut impl Read) -> Result<Vec<SideBandPacket>> {
1273 let frames = read_pkt_line_frames_until_flush(reader)?;
1274 parse_sideband_stream(&frames)
1275}
1276
1277pub fn write_sideband_stream(writer: &mut impl Write, packets: &[SideBandPacket]) -> Result<()> {
1278 for packet in packets {
1279 write_sideband_packet(writer, packet)?;
1280 }
1281 writer.write_all(b"0000")?;
1282 Ok(())
1283}
1284
1285pub fn demux_sideband_packets(packets: &[SideBandPacket]) -> Result<SideBandDemux> {
1286 let mut out = SideBandDemux::default();
1287 for packet in packets {
1288 match packet.channel {
1289 SideBandChannel::Data => out.data.extend_from_slice(&packet.data),
1290 SideBandChannel::Progress => out.progress.push(packet.data.clone()),
1291 SideBandChannel::Fatal => {
1292 let message = String::from_utf8_lossy(&packet.data).into_owned();
1293 return Err(GitError::InvalidFormat(format!(
1294 "sideband fatal: {message}"
1295 )));
1296 }
1297 }
1298 }
1299 Ok(out)
1300}
1301
1302pub fn parse_and_demux_sideband_packets(payloads: &[Vec<u8>]) -> Result<SideBandDemux> {
1303 let packets = parse_sideband_packets(payloads)?;
1304 demux_sideband_packets(&packets)
1305}
1306
1307pub fn demux_sideband_stream(frames: &[PktLineFrame]) -> Result<SideBandDemux> {
1308 let packets = parse_sideband_stream(frames)?;
1309 demux_sideband_packets(&packets)
1310}
1311
1312pub fn read_and_demux_sideband_stream(reader: &mut impl Read) -> Result<SideBandDemux> {
1313 let packets = read_sideband_stream(reader)?;
1314 demux_sideband_packets(&packets)
1315}
1316
1317pub fn parse_upload_archive_request(frames: &[PktLineFrame]) -> Result<UploadArchiveRequest> {
1318 let mut request = UploadArchiveRequest::default();
1319 let mut saw_flush = false;
1320 for (idx, frame) in frames.iter().enumerate() {
1321 match frame {
1322 PktLineFrame::Data(payload) if !saw_flush => {
1323 let text = parse_protocol_v2_line_text("upload-archive request argument", payload)?;
1324 let argument = text.strip_prefix("argument ").ok_or_else(|| {
1325 GitError::InvalidFormat("upload-archive request line must be argument".into())
1326 })?;
1327 validate_upload_archive_argument(argument)?;
1328 request.arguments.push(argument.to_string());
1329 }
1330 PktLineFrame::Data(_) => {
1331 return Err(GitError::InvalidFormat(
1332 "upload-archive request has data after flush".into(),
1333 ));
1334 }
1335 PktLineFrame::Flush => {
1336 saw_flush = true;
1337 if idx + 1 != frames.len() {
1338 return Err(GitError::InvalidFormat(
1339 "upload-archive request has frames after flush".into(),
1340 ));
1341 }
1342 }
1343 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1344 return Err(GitError::InvalidFormat(
1345 "upload-archive request contains a non-flush control packet".into(),
1346 ));
1347 }
1348 }
1349 }
1350 if !saw_flush {
1351 return Err(GitError::InvalidFormat(
1352 "upload-archive request missing flush".into(),
1353 ));
1354 }
1355 if request.arguments.is_empty() {
1356 return Err(GitError::InvalidFormat(
1357 "upload-archive request is missing arguments".into(),
1358 ));
1359 }
1360 Ok(request)
1361}
1362
1363pub fn encode_upload_archive_request(request: &UploadArchiveRequest) -> Result<Vec<PktLineFrame>> {
1364 if request.arguments.is_empty() {
1365 return Err(GitError::InvalidFormat(
1366 "upload-archive request is missing arguments".into(),
1367 ));
1368 }
1369 let mut frames = Vec::new();
1370 for argument in &request.arguments {
1371 validate_upload_archive_argument(argument)?;
1372 frames.push(PktLineFrame::data(line_from_str(&format!(
1373 "argument {argument}"
1374 )))?);
1375 }
1376 frames.push(PktLineFrame::Flush);
1377 Ok(frames)
1378}
1379
1380pub fn read_upload_archive_request(reader: &mut impl Read) -> Result<UploadArchiveRequest> {
1381 let frames = read_pkt_line_frames_until_flush(reader)?;
1382 parse_upload_archive_request(&frames)
1383}
1384
1385pub fn write_upload_archive_request(
1386 writer: &mut impl Write,
1387 request: &UploadArchiveRequest,
1388) -> Result<()> {
1389 if request.arguments.is_empty() {
1390 return Err(GitError::InvalidFormat(
1391 "upload-archive request is missing arguments".into(),
1392 ));
1393 }
1394 for argument in &request.arguments {
1395 validate_upload_archive_argument(argument)?;
1396 write_pkt_line_payload(writer, &line_from_str(&format!("argument {argument}")))?;
1397 }
1398 writer.write_all(b"0000")?;
1399 Ok(())
1400}
1401
1402pub fn parse_upload_archive_response(frames: &[PktLineFrame]) -> Result<UploadArchiveResponse> {
1403 let Some((first, rest)) = frames.split_first() else {
1404 return Err(GitError::InvalidFormat(
1405 "upload-archive response is empty".into(),
1406 ));
1407 };
1408 let PktLineFrame::Data(payload) = first else {
1409 return Err(GitError::InvalidFormat(
1410 "upload-archive response must start with a data packet".into(),
1411 ));
1412 };
1413 let text = parse_protocol_v2_line_text("upload-archive response status", payload)?;
1414 if text == "ACK" {
1415 return Ok(UploadArchiveResponse::Ack {
1416 sideband: parse_sideband_stream(rest)?,
1417 });
1418 }
1419 if let Some(message) = text.strip_prefix("NACK ") {
1420 validate_upload_archive_status_message(message)?;
1421 if !matches!(rest, [PktLineFrame::Flush]) {
1422 return Err(GitError::InvalidFormat(
1423 "upload-archive NACK response must end with flush".into(),
1424 ));
1425 }
1426 return Ok(UploadArchiveResponse::Nack {
1427 message: message.to_string(),
1428 });
1429 }
1430 Err(GitError::InvalidFormat(format!(
1431 "unsupported upload-archive response status {text}"
1432 )))
1433}
1434
1435pub fn encode_upload_archive_response(
1436 response: &UploadArchiveResponse,
1437) -> Result<Vec<PktLineFrame>> {
1438 let mut frames = Vec::new();
1439 match response {
1440 UploadArchiveResponse::Ack { sideband } => {
1441 frames.push(PktLineFrame::data(line_from_str("ACK"))?);
1442 frames.extend(encode_sideband_stream(sideband)?);
1443 }
1444 UploadArchiveResponse::Nack { message } => {
1445 validate_upload_archive_status_message(message)?;
1446 frames.push(PktLineFrame::data(line_from_str(&format!(
1447 "NACK {message}"
1448 )))?);
1449 frames.push(PktLineFrame::Flush);
1450 }
1451 }
1452 Ok(frames)
1453}
1454
1455pub fn read_upload_archive_response(reader: &mut impl Read) -> Result<UploadArchiveResponse> {
1456 let frames = read_pkt_line_frames_until_flush(reader)?;
1457 parse_upload_archive_response(&frames)
1458}
1459
1460pub fn write_upload_archive_response(
1461 writer: &mut impl Write,
1462 response: &UploadArchiveResponse,
1463) -> Result<()> {
1464 match response {
1465 UploadArchiveResponse::Ack { sideband } => {
1466 write_pkt_line_payload(writer, b"ACK\n")?;
1467 write_sideband_stream(writer, sideband)?;
1468 }
1469 UploadArchiveResponse::Nack { message } => {
1470 validate_upload_archive_status_message(message)?;
1471 write_pkt_line_payload(writer, &line_from_str(&format!("NACK {message}")))?;
1472 writer.write_all(b"0000")?;
1473 }
1474 }
1475 Ok(())
1476}
1477
1478pub fn demux_upload_archive_response(response: &UploadArchiveResponse) -> Result<SideBandDemux> {
1479 match response {
1480 UploadArchiveResponse::Ack { sideband } => demux_sideband_packets(sideband),
1481 UploadArchiveResponse::Nack { message } => Err(GitError::InvalidFormat(format!(
1482 "upload-archive NACK: {message}"
1483 ))),
1484 }
1485}
1486
1487fn parse_pkt_len(bytes: &[u8]) -> Result<usize> {
1488 let mut len = 0usize;
1489 for byte in bytes {
1490 len = (len << 4) | hex_nibble(*byte)? as usize;
1491 }
1492 Ok(len)
1493}
1494
1495fn hex_nibble(byte: u8) -> Result<u8> {
1496 match byte {
1497 b'0'..=b'9' => Ok(byte - b'0'),
1498 b'a'..=b'f' => Ok(byte - b'a' + 10),
1499 b'A'..=b'F' => Ok(byte - b'A' + 10),
1500 _ => Err(GitError::InvalidFormat(format!(
1501 "invalid pkt-line length byte {byte:#04x}"
1502 ))),
1503 }
1504}
1505
1506#[derive(Debug, Clone, PartialEq, Eq)]
1507pub struct TransportHandshake {
1508 pub protocol: ProtocolVersion,
1509 pub capabilities: Vec<Capability>,
1510}
1511
1512#[derive(Debug, Clone, PartialEq, Eq)]
1513pub struct RefAdvertisement {
1514 pub oid: ObjectId,
1515 pub name: String,
1516 pub capabilities: Vec<Capability>,
1517}
1518
1519#[derive(Debug, Clone, PartialEq, Eq)]
1520pub struct DumbHttpRefRecord {
1521 pub oid: ObjectId,
1522 pub name: String,
1523 pub peeled: bool,
1524}
1525
1526#[derive(Debug, Clone, PartialEq, Eq)]
1527pub struct DumbHttpPackRecord {
1528 pub hash: ObjectId,
1529}
1530
1531#[derive(Debug, Clone, PartialEq, Eq)]
1532pub struct RefAdvertisementSet {
1533 pub protocol: ProtocolVersion,
1534 pub refs: Vec<RefAdvertisement>,
1535 pub shallow: Vec<ObjectId>,
1536}
1537
1538#[derive(Debug, Clone, PartialEq, Eq, Default)]
1539pub struct UploadPackRequest {
1540 pub wants: Vec<ObjectId>,
1541 pub capabilities: Vec<Capability>,
1542 pub shallow: Vec<ObjectId>,
1543 pub deepen: Option<u32>,
1544 pub deepen_since: Option<u64>,
1545 pub deepen_not: Vec<String>,
1546 pub filter: Option<String>,
1547}
1548
1549#[derive(Debug, Clone, PartialEq, Eq, Default)]
1550pub struct UploadPackFeatures {
1551 pub multi_ack: bool,
1552 pub multi_ack_detailed: bool,
1553 pub no_done: bool,
1554 pub thin_pack: bool,
1555 pub side_band: bool,
1556 pub side_band_64k: bool,
1557 pub ofs_delta: bool,
1558 pub shallow: bool,
1559 pub deepen_since: bool,
1560 pub deepen_not: bool,
1561 pub include_tag: bool,
1562 pub no_progress: bool,
1563 pub allow_tip_sha1_in_want: bool,
1564 pub allow_reachable_sha1_in_want: bool,
1565 pub filter: bool,
1566 pub agent: Option<String>,
1567 pub object_format: Option<ObjectFormat>,
1568 pub symrefs: Vec<String>,
1569 pub unknown: Vec<Capability>,
1570}
1571
1572#[derive(Debug, Clone, PartialEq, Eq, Default)]
1573pub struct UploadPackNegotiationRequest {
1574 pub haves: Vec<ObjectId>,
1575 pub done: bool,
1576}
1577
1578#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1579pub enum UploadPackAckStatus {
1580 Continue,
1581 Common,
1582 Ready,
1583}
1584
1585#[derive(Debug, Clone, PartialEq, Eq)]
1586pub enum UploadPackAcknowledgment {
1587 Nak,
1588 Ack {
1589 oid: ObjectId,
1590 status: Option<UploadPackAckStatus>,
1591 },
1592}
1593
1594#[derive(Debug, Clone, PartialEq, Eq, Default)]
1595pub struct UploadPackPackfileResponse {
1596 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1597 pub sideband: Vec<SideBandPacket>,
1598}
1599
1600#[derive(Debug, Clone, PartialEq, Eq, Default)]
1601pub struct UploadPackRawPackfileResponse {
1602 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1603 pub packfile: Vec<u8>,
1604}
1605
1606#[derive(Debug, Clone, PartialEq, Eq)]
1607pub struct ReceivePackCommand {
1608 pub old_id: ObjectId,
1609 pub new_id: ObjectId,
1610 pub name: String,
1611}
1612
1613#[derive(Debug, Clone, PartialEq, Eq, Default)]
1614pub struct ReceivePackRequest {
1615 pub shallow: Vec<ObjectId>,
1616 pub commands: Vec<ReceivePackCommand>,
1617 pub capabilities: Vec<Capability>,
1618}
1619
1620#[derive(Debug, Clone, PartialEq, Eq, Default)]
1621pub struct ReceivePackPushRequest {
1622 pub commands: ReceivePackRequest,
1623 pub push_options: Option<Vec<String>>,
1624 pub packfile: Vec<u8>,
1625}
1626
1627#[derive(Debug, Clone, PartialEq, Eq, Default)]
1628pub struct ReceivePackPushRequestOptions {
1629 pub report_status: bool,
1630 pub report_status_v2: bool,
1631 pub atomic: bool,
1632 pub ofs_delta: bool,
1633 pub side_band_64k: bool,
1634 pub quiet: bool,
1635 pub agent: Option<String>,
1636 pub object_format: Option<ObjectFormat>,
1637 pub push_options: Vec<String>,
1638}
1639
1640#[derive(Debug, Clone, PartialEq, Eq, Default)]
1641pub struct ReceivePackFeatures {
1642 pub report_status: bool,
1643 pub report_status_v2: bool,
1644 pub delete_refs: bool,
1645 pub ofs_delta: bool,
1646 pub atomic: bool,
1647 pub push_options: bool,
1648 pub side_band_64k: bool,
1649 pub quiet: bool,
1650 pub no_thin: bool,
1651 pub agent: Option<String>,
1652 pub object_format: Option<ObjectFormat>,
1653 pub unknown: Vec<Capability>,
1654}
1655
1656#[derive(Debug, Clone, PartialEq, Eq)]
1657pub enum ReceivePackUnpackStatus {
1658 Ok,
1659 Error(String),
1660}
1661
1662#[derive(Debug, Clone, PartialEq, Eq)]
1663pub enum ReceivePackCommandStatus {
1664 Ok { name: String },
1665 Ng { name: String, message: String },
1666}
1667
1668#[derive(Debug, Clone, PartialEq, Eq)]
1669pub struct ReceivePackReportStatus {
1670 pub unpack: ReceivePackUnpackStatus,
1671 pub commands: Vec<ReceivePackCommandStatus>,
1672}
1673
1674#[derive(Debug, Clone, PartialEq, Eq, Default)]
1675pub struct ReceivePackCommandStatusV2Options {
1676 pub refname: Option<String>,
1677 pub old_oid: Option<ObjectId>,
1678 pub new_oid: Option<ObjectId>,
1679 pub forced_update: bool,
1680}
1681
1682#[derive(Debug, Clone, PartialEq, Eq)]
1683pub enum ReceivePackCommandStatusV2 {
1684 Ok {
1685 name: String,
1686 options: ReceivePackCommandStatusV2Options,
1687 },
1688 Ng {
1689 name: String,
1690 message: String,
1691 },
1692}
1693
1694#[derive(Debug, Clone, PartialEq, Eq)]
1695pub struct ReceivePackReportStatusV2 {
1696 pub unpack: ReceivePackUnpackStatus,
1697 pub commands: Vec<ReceivePackCommandStatusV2>,
1698}
1699
1700#[derive(Debug, Clone, PartialEq, Eq)]
1701pub struct ProtocolV2CommandRequest {
1702 pub command: String,
1703 pub capabilities: Vec<Capability>,
1704 pub arguments: Vec<Vec<u8>>,
1705}
1706
1707#[derive(Debug, Clone, PartialEq, Eq)]
1708pub enum ProtocolV2Request {
1709 Command(ProtocolV2CommandRequest),
1710 Done,
1711}
1712
1713#[derive(Debug, Clone, PartialEq, Eq)]
1714pub enum ProtocolV2Command {
1715 LsRefs(ProtocolV2LsRefsRequest),
1716 Fetch(ProtocolV2FetchRequest),
1717 ObjectInfo(ProtocolV2ObjectInfoRequest),
1718 Unknown(ProtocolV2CommandRequest),
1719}
1720
1721#[derive(Debug, Clone, PartialEq, Eq)]
1722pub enum ProtocolV2SessionRequest {
1723 Command(ProtocolV2Command),
1724 Done,
1725}
1726
1727#[derive(Debug, Clone, PartialEq, Eq, Default)]
1728pub struct ProtocolV2CommandOptions {
1729 pub agent: Option<String>,
1730 pub object_format: Option<ObjectFormat>,
1731 pub server_options: Vec<String>,
1732 pub extra: Vec<Capability>,
1733}
1734
1735#[derive(Debug, Clone, PartialEq, Eq, Default)]
1736pub struct ProtocolV2FetchFeatures {
1737 pub shallow: bool,
1738 pub wait_for_done: bool,
1739 pub filter: bool,
1740 pub ref_in_want: bool,
1741 pub sideband_all: bool,
1742 pub packfile_uris: bool,
1743 pub unknown: Vec<String>,
1744}
1745
1746#[derive(Debug, Clone, PartialEq, Eq, Default)]
1747pub struct ProtocolV2LsRefsFeatures {
1748 pub unborn: bool,
1749 pub unknown: Vec<String>,
1750}
1751
1752impl ProtocolV2CommandRequest {
1753 pub fn new(command: impl Into<String>) -> Result<Self> {
1754 let command = command.into();
1755 validate_capability_name(&command)?;
1756 Ok(Self {
1757 command,
1758 capabilities: Vec::new(),
1759 arguments: Vec::new(),
1760 })
1761 }
1762}
1763
1764#[derive(Debug, Clone, PartialEq, Eq, Default)]
1765pub struct ProtocolV2LsRefsRequest {
1766 pub peel: bool,
1767 pub symrefs: bool,
1768 pub unborn: bool,
1769 pub ref_prefixes: Vec<String>,
1770}
1771
1772#[derive(Debug, Clone, PartialEq, Eq)]
1773pub struct ProtocolV2LsRefsRef {
1774 pub oid: ObjectId,
1775 pub name: String,
1776 pub peeled: Option<ObjectId>,
1777 pub symref_target: Option<String>,
1778 pub attributes: Vec<String>,
1779}
1780
1781#[derive(Debug, Clone, PartialEq, Eq)]
1782pub enum ProtocolV2LsRefsRecord {
1783 Ref(ProtocolV2LsRefsRef),
1784 Unborn {
1785 name: String,
1786 symref_target: Option<String>,
1787 attributes: Vec<String>,
1788 },
1789}
1790
1791#[derive(Debug, Clone, PartialEq, Eq, Default)]
1792pub struct ProtocolV2FetchRequest {
1793 pub wants: Vec<ObjectId>,
1794 pub want_refs: Vec<String>,
1795 pub haves: Vec<ObjectId>,
1796 pub shallow: Vec<ObjectId>,
1797 pub deepen: Option<u32>,
1798 pub deepen_since: Option<u64>,
1799 pub deepen_not: Vec<String>,
1800 pub deepen_relative: bool,
1801 pub filter: Option<String>,
1802 pub packfile_uris: Option<String>,
1803 pub thin_pack: bool,
1804 pub no_progress: bool,
1805 pub include_tag: bool,
1806 pub ofs_delta: bool,
1807 pub sideband_all: bool,
1808 pub wait_for_done: bool,
1809 pub done: bool,
1810}
1811
1812#[derive(Debug, Clone, PartialEq, Eq)]
1813pub enum ProtocolV2FetchAcknowledgment {
1814 Nak,
1815 Ack(ObjectId),
1816 Ready,
1817}
1818
1819#[derive(Debug, Clone, PartialEq, Eq)]
1820pub enum ProtocolV2FetchShallowInfo {
1821 Shallow(ObjectId),
1822 Unshallow(ObjectId),
1823}
1824
1825#[derive(Debug, Clone, PartialEq, Eq)]
1826pub struct ProtocolV2FetchWantedRef {
1827 pub oid: ObjectId,
1828 pub name: String,
1829}
1830
1831#[derive(Debug, Clone, PartialEq, Eq)]
1832pub struct ProtocolV2FetchPackfileUri {
1833 pub pack_hash: ObjectId,
1834 pub uri: String,
1835}
1836
1837#[derive(Debug, Clone, PartialEq, Eq)]
1838pub enum ProtocolV2FetchResponseSection {
1839 Acknowledgments(Vec<ProtocolV2FetchAcknowledgment>),
1840 ShallowInfo(Vec<ProtocolV2FetchShallowInfo>),
1841 WantedRefs(Vec<ProtocolV2FetchWantedRef>),
1842 PackfileUris(Vec<ProtocolV2FetchPackfileUri>),
1843 Packfile(Vec<Vec<u8>>),
1844 Unknown { name: String, lines: Vec<Vec<u8>> },
1845}
1846
1847#[derive(Debug, Clone, PartialEq, Eq, Default)]
1848pub struct ProtocolV2FetchSidebandAllResponse {
1849 pub sections: Vec<ProtocolV2FetchResponseSection>,
1850 pub progress: Vec<Vec<u8>>,
1851}
1852
1853#[derive(Debug, Clone, PartialEq, Eq, Default)]
1854pub struct ProtocolV2ObjectInfoRequest {
1855 pub size: bool,
1856 pub oids: Vec<ObjectId>,
1857}
1858
1859#[derive(Debug, Clone, PartialEq, Eq)]
1860pub struct ProtocolV2ObjectInfoRecord {
1861 pub oid: ObjectId,
1862 pub size: u64,
1863}
1864
1865#[derive(Debug, Clone, PartialEq, Eq, Default)]
1866pub struct ProtocolV2ObjectInfoResponse {
1867 pub size: bool,
1868 pub records: Vec<ProtocolV2ObjectInfoRecord>,
1869}
1870
1871impl ProtocolV2LsRefsRequest {
1872 pub fn from_command_request(request: &ProtocolV2CommandRequest) -> Result<Self> {
1873 if request.command != "ls-refs" {
1874 return Err(GitError::InvalidFormat(format!(
1875 "expected ls-refs command, got {}",
1876 request.command
1877 )));
1878 }
1879 let mut out = Self::default();
1880 for argument in &request.arguments {
1881 let text = std::str::from_utf8(argument)
1882 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1883 match text {
1884 "peel" => out.peel = true,
1885 "symrefs" => out.symrefs = true,
1886 "unborn" => out.unborn = true,
1887 value if value.starts_with("ref-prefix ") => {
1888 let prefix = value
1889 .strip_prefix("ref-prefix ")
1890 .ok_or_else(|| GitError::InvalidFormat("invalid ref-prefix".into()))?;
1891 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1892 out.ref_prefixes.push(prefix.to_string());
1893 }
1894 other => {
1895 return Err(GitError::InvalidFormat(format!(
1896 "unsupported ls-refs argument {other}"
1897 )));
1898 }
1899 }
1900 }
1901 Ok(out)
1902 }
1903
1904 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1905 let mut request = ProtocolV2CommandRequest::new("ls-refs")?;
1906 if self.peel {
1907 request.arguments.push(b"peel".to_vec());
1908 }
1909 if self.symrefs {
1910 request.arguments.push(b"symrefs".to_vec());
1911 }
1912 if self.unborn {
1913 request.arguments.push(b"unborn".to_vec());
1914 }
1915 for prefix in &self.ref_prefixes {
1916 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1917 request
1918 .arguments
1919 .push(format!("ref-prefix {prefix}").into_bytes());
1920 }
1921 Ok(request)
1922 }
1923}
1924
1925impl ProtocolV2FetchRequest {
1926 pub fn from_command_request(
1927 format: ObjectFormat,
1928 request: &ProtocolV2CommandRequest,
1929 ) -> Result<Self> {
1930 if request.command != "fetch" {
1931 return Err(GitError::InvalidFormat(format!(
1932 "expected fetch command, got {}",
1933 request.command
1934 )));
1935 }
1936 let mut out = Self::default();
1937 for argument in &request.arguments {
1938 let text = std::str::from_utf8(argument)
1939 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1940 match text {
1941 "thin-pack" => out.thin_pack = true,
1942 "no-progress" => out.no_progress = true,
1943 "include-tag" => out.include_tag = true,
1944 "ofs-delta" => out.ofs_delta = true,
1945 "sideband-all" => out.sideband_all = true,
1946 "wait-for-done" => out.wait_for_done = true,
1947 "deepen-relative" => out.deepen_relative = true,
1948 "done" => out.done = true,
1949 value if value.starts_with("want ") => {
1950 out.wants
1951 .push(parse_oid_argument(format, "fetch want", value, "want ")?);
1952 }
1953 value if value.starts_with("want-ref ") => {
1954 let name = value
1955 .strip_prefix("want-ref ")
1956 .ok_or_else(|| GitError::InvalidFormat("invalid fetch want-ref".into()))?;
1957 validate_protocol_v2_token("fetch want-ref", name)?;
1958 out.want_refs.push(name.to_string());
1959 }
1960 value if value.starts_with("have ") => {
1961 out.haves
1962 .push(parse_oid_argument(format, "fetch have", value, "have ")?);
1963 }
1964 value if value.starts_with("shallow ") => {
1965 out.shallow.push(parse_oid_argument(
1966 format,
1967 "fetch shallow",
1968 value,
1969 "shallow ",
1970 )?);
1971 }
1972 value if value.starts_with("deepen ") => {
1973 if out.deepen.is_some() {
1974 return Err(GitError::InvalidFormat(
1975 "fetch request has duplicate deepen".into(),
1976 ));
1977 }
1978 out.deepen = Some(parse_u32_argument("fetch deepen", value, "deepen ")?);
1979 }
1980 value if value.starts_with("deepen-since ") => {
1981 if out.deepen_since.is_some() {
1982 return Err(GitError::InvalidFormat(
1983 "fetch request has duplicate deepen-since".into(),
1984 ));
1985 }
1986 out.deepen_since = Some(parse_u64_argument(
1987 "fetch deepen-since",
1988 value,
1989 "deepen-since ",
1990 )?);
1991 }
1992 value if value.starts_with("deepen-not ") => {
1993 let name = value.strip_prefix("deepen-not ").ok_or_else(|| {
1994 GitError::InvalidFormat("invalid fetch deepen-not".into())
1995 })?;
1996 validate_protocol_v2_token("fetch deepen-not", name)?;
1997 out.deepen_not.push(name.to_string());
1998 }
1999 value if value.starts_with("filter ") => {
2000 if out.filter.is_some() {
2001 return Err(GitError::InvalidFormat(
2002 "fetch request has duplicate filter".into(),
2003 ));
2004 }
2005 let filter = value
2006 .strip_prefix("filter ")
2007 .ok_or_else(|| GitError::InvalidFormat("invalid fetch filter".into()))?;
2008 validate_protocol_v2_token("fetch filter", filter)?;
2009 out.filter = Some(filter.to_string());
2010 }
2011 value if value.starts_with("packfile-uris ") => {
2012 if out.packfile_uris.is_some() {
2013 return Err(GitError::InvalidFormat(
2014 "fetch request has duplicate packfile-uris".into(),
2015 ));
2016 }
2017 let protocols = value.strip_prefix("packfile-uris ").ok_or_else(|| {
2018 GitError::InvalidFormat("invalid fetch packfile-uris".into())
2019 })?;
2020 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
2021 out.packfile_uris = Some(protocols.to_string());
2022 }
2023 other => {
2024 return Err(GitError::InvalidFormat(format!(
2025 "unsupported fetch argument {other}"
2026 )));
2027 }
2028 }
2029 }
2030 Ok(out)
2031 }
2032
2033 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
2034 let mut request = ProtocolV2CommandRequest::new("fetch")?;
2035 for oid in &self.wants {
2036 request.arguments.push(format!("want {oid}").into_bytes());
2037 }
2038 for name in &self.want_refs {
2039 validate_protocol_v2_token("fetch want-ref", name)?;
2040 request
2041 .arguments
2042 .push(format!("want-ref {name}").into_bytes());
2043 }
2044 for oid in &self.haves {
2045 request.arguments.push(format!("have {oid}").into_bytes());
2046 }
2047 for oid in &self.shallow {
2048 request
2049 .arguments
2050 .push(format!("shallow {oid}").into_bytes());
2051 }
2052 if let Some(deepen) = self.deepen {
2053 if deepen == 0 {
2054 return Err(GitError::InvalidFormat(
2055 "fetch deepen must be positive".into(),
2056 ));
2057 }
2058 request
2059 .arguments
2060 .push(format!("deepen {deepen}").into_bytes());
2061 }
2062 if let Some(deepen_since) = self.deepen_since {
2063 request
2064 .arguments
2065 .push(format!("deepen-since {deepen_since}").into_bytes());
2066 }
2067 for name in &self.deepen_not {
2068 validate_protocol_v2_token("fetch deepen-not", name)?;
2069 request
2070 .arguments
2071 .push(format!("deepen-not {name}").into_bytes());
2072 }
2073 if self.deepen_relative {
2074 request.arguments.push(b"deepen-relative".to_vec());
2075 }
2076 if let Some(filter) = &self.filter {
2077 validate_protocol_v2_token("fetch filter", filter)?;
2078 request
2079 .arguments
2080 .push(format!("filter {filter}").into_bytes());
2081 }
2082 if let Some(protocols) = &self.packfile_uris {
2083 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
2084 request
2085 .arguments
2086 .push(format!("packfile-uris {protocols}").into_bytes());
2087 }
2088 if self.thin_pack {
2089 request.arguments.push(b"thin-pack".to_vec());
2090 }
2091 if self.no_progress {
2092 request.arguments.push(b"no-progress".to_vec());
2093 }
2094 if self.include_tag {
2095 request.arguments.push(b"include-tag".to_vec());
2096 }
2097 if self.ofs_delta {
2098 request.arguments.push(b"ofs-delta".to_vec());
2099 }
2100 if self.sideband_all {
2101 request.arguments.push(b"sideband-all".to_vec());
2102 }
2103 if self.wait_for_done {
2104 request.arguments.push(b"wait-for-done".to_vec());
2105 }
2106 if self.done {
2107 request.arguments.push(b"done".to_vec());
2108 }
2109 Ok(request)
2110 }
2111}
2112
2113impl ProtocolV2ObjectInfoRequest {
2114 pub fn from_command_request(
2115 format: ObjectFormat,
2116 request: &ProtocolV2CommandRequest,
2117 ) -> Result<Self> {
2118 if request.command != "object-info" {
2119 return Err(GitError::InvalidFormat(format!(
2120 "expected object-info command, got {}",
2121 request.command
2122 )));
2123 }
2124 let mut out = Self::default();
2125 for argument in &request.arguments {
2126 let text = parse_protocol_v2_line_text("object-info request argument", argument)?;
2127 if text == "size" {
2128 if out.size {
2129 return Err(GitError::InvalidFormat(
2130 "object-info request has duplicate size argument".into(),
2131 ));
2132 }
2133 out.size = true;
2134 } else if text.starts_with("oid ") {
2135 out.oids
2136 .push(parse_oid_argument(format, "object-info oid", text, "oid ")?);
2137 } else {
2138 return Err(GitError::InvalidFormat(format!(
2139 "unsupported object-info request argument {text}"
2140 )));
2141 }
2142 }
2143 if !out.size {
2144 return Err(GitError::InvalidFormat(
2145 "object-info request is missing size argument".into(),
2146 ));
2147 }
2148 if out.oids.is_empty() {
2149 return Err(GitError::InvalidFormat(
2150 "object-info request is missing object ids".into(),
2151 ));
2152 }
2153 Ok(out)
2154 }
2155
2156 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
2157 if !self.size {
2158 return Err(GitError::InvalidFormat(
2159 "object-info request is missing size argument".into(),
2160 ));
2161 }
2162 if self.oids.is_empty() {
2163 return Err(GitError::InvalidFormat(
2164 "object-info request is missing object ids".into(),
2165 ));
2166 }
2167 let mut request = ProtocolV2CommandRequest::new("object-info")?;
2168 request.arguments.push(b"size".to_vec());
2169 for oid in &self.oids {
2170 request.arguments.push(format!("oid {oid}").into_bytes());
2171 }
2172 Ok(request)
2173 }
2174}
2175
2176pub fn parse_protocol_v2_advertisement(frames: &[PktLineFrame]) -> Result<TransportHandshake> {
2177 let Some((first, rest)) = frames.split_first() else {
2178 return Err(GitError::InvalidFormat(
2179 "protocol v2 advertisement is empty".into(),
2180 ));
2181 };
2182 match first {
2183 PktLineFrame::Data(payload) if trim_trailing_lf(payload) == b"version 2" => {}
2184 PktLineFrame::Data(_) => {
2185 return Err(GitError::InvalidFormat(
2186 "protocol v2 advertisement missing version line".into(),
2187 ));
2188 }
2189 _ => {
2190 return Err(GitError::InvalidFormat(
2191 "protocol v2 advertisement must start with a data line".into(),
2192 ));
2193 }
2194 }
2195
2196 let mut capabilities = Vec::new();
2197 let mut saw_flush = false;
2198 for (idx, frame) in rest.iter().enumerate() {
2199 match frame {
2200 PktLineFrame::Data(payload) => {
2201 if saw_flush {
2202 return Err(GitError::InvalidFormat(
2203 "protocol v2 advertisement has data after flush".into(),
2204 ));
2205 }
2206 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2207 }
2208 PktLineFrame::Flush => {
2209 saw_flush = true;
2210 if idx + 1 != rest.len() {
2211 return Err(GitError::InvalidFormat(
2212 "protocol v2 advertisement has frames after flush".into(),
2213 ));
2214 }
2215 }
2216 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2217 return Err(GitError::InvalidFormat(
2218 "protocol v2 advertisement contains a non-flush control packet".into(),
2219 ));
2220 }
2221 }
2222 }
2223 if !saw_flush {
2224 return Err(GitError::InvalidFormat(
2225 "protocol v2 advertisement missing flush".into(),
2226 ));
2227 }
2228
2229 Ok(TransportHandshake {
2230 protocol: ProtocolVersion::V2,
2231 capabilities,
2232 })
2233}
2234
2235pub fn encode_protocol_v2_advertisement(
2236 handshake: &TransportHandshake,
2237) -> Result<Vec<PktLineFrame>> {
2238 if handshake.protocol != ProtocolVersion::V2 {
2239 return Err(GitError::InvalidFormat(
2240 "protocol v2 advertisement requires a v2 handshake".into(),
2241 ));
2242 }
2243 let mut frames = vec![PktLineFrame::data(line_from_str("version 2"))?];
2244 for capability in &handshake.capabilities {
2245 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2246 capability,
2247 )?))?);
2248 }
2249 frames.push(PktLineFrame::Flush);
2250 Ok(frames)
2251}
2252
2253pub fn read_protocol_v2_advertisement(reader: &mut impl Read) -> Result<TransportHandshake> {
2254 let frames = read_pkt_line_frames_until_flush(reader)?;
2255 parse_protocol_v2_advertisement(&frames)
2256}
2257
2258pub fn write_protocol_v2_advertisement(
2259 writer: &mut impl Write,
2260 handshake: &TransportHandshake,
2261) -> Result<()> {
2262 if handshake.protocol != ProtocolVersion::V2 {
2263 return Err(GitError::InvalidFormat(
2264 "protocol v2 advertisement requires a v2 handshake".into(),
2265 ));
2266 }
2267 write_pkt_line_payload(writer, b"version 2\n")?;
2268 for capability in &handshake.capabilities {
2269 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2270 }
2271 writer.write_all(b"0000")?;
2272 Ok(())
2273}
2274
2275pub fn parse_protocol_v2_command_request(
2276 frames: &[PktLineFrame],
2277) -> Result<ProtocolV2CommandRequest> {
2278 let Some((first, rest)) = frames.split_first() else {
2279 return Err(GitError::InvalidFormat(
2280 "protocol v2 command request is empty".into(),
2281 ));
2282 };
2283 let command = match first {
2284 PktLineFrame::Data(payload) => parse_protocol_v2_command_line(payload)?,
2285 _ => {
2286 return Err(GitError::InvalidFormat(
2287 "protocol v2 command request must start with a command line".into(),
2288 ));
2289 }
2290 };
2291
2292 let mut capabilities = Vec::new();
2293 let mut arguments = Vec::new();
2294 let mut in_arguments = false;
2295 let mut saw_flush = false;
2296 for (idx, frame) in rest.iter().enumerate() {
2297 match frame {
2298 PktLineFrame::Data(payload) if !in_arguments => {
2299 if saw_flush {
2300 return Err(GitError::InvalidFormat(
2301 "protocol v2 command request has data after flush".into(),
2302 ));
2303 }
2304 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2305 }
2306 PktLineFrame::Data(payload) => {
2307 if saw_flush {
2308 return Err(GitError::InvalidFormat(
2309 "protocol v2 command request has data after flush".into(),
2310 ));
2311 }
2312 let argument = trim_trailing_lf(payload);
2313 if argument.is_empty() {
2314 return Err(GitError::InvalidFormat(
2315 "protocol v2 command argument is empty".into(),
2316 ));
2317 }
2318 if argument
2319 .iter()
2320 .any(|byte| matches!(*byte, b'\n' | b'\r' | 0))
2321 {
2322 return Err(GitError::InvalidFormat(
2323 "protocol v2 command argument contains a delimiter byte".into(),
2324 ));
2325 }
2326 arguments.push(argument.to_vec());
2327 }
2328 PktLineFrame::Delimiter => {
2329 if in_arguments {
2330 return Err(GitError::InvalidFormat(
2331 "protocol v2 command request has duplicate delimiter".into(),
2332 ));
2333 }
2334 if saw_flush {
2335 return Err(GitError::InvalidFormat(
2336 "protocol v2 command request has delimiter after flush".into(),
2337 ));
2338 }
2339 in_arguments = true;
2340 }
2341 PktLineFrame::Flush => {
2342 saw_flush = true;
2343 if idx + 1 != rest.len() {
2344 return Err(GitError::InvalidFormat(
2345 "protocol v2 command request has frames after flush".into(),
2346 ));
2347 }
2348 }
2349 PktLineFrame::ResponseEnd => {
2350 return Err(GitError::InvalidFormat(
2351 "protocol v2 command request contains response-end".into(),
2352 ));
2353 }
2354 }
2355 }
2356 if !saw_flush {
2357 return Err(GitError::InvalidFormat(
2358 "protocol v2 command request missing flush".into(),
2359 ));
2360 }
2361
2362 Ok(ProtocolV2CommandRequest {
2363 command,
2364 capabilities,
2365 arguments,
2366 })
2367}
2368
2369pub fn encode_protocol_v2_command_request(
2370 request: &ProtocolV2CommandRequest,
2371) -> Result<Vec<PktLineFrame>> {
2372 validate_capability_name(&request.command)?;
2373 let mut frames = Vec::new();
2374 frames.push(PktLineFrame::data(line_from_str(&format!(
2375 "command={}",
2376 request.command
2377 )))?);
2378 for capability in &request.capabilities {
2379 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2380 capability,
2381 )?))?);
2382 }
2383 if !request.arguments.is_empty() {
2384 frames.push(PktLineFrame::Delimiter);
2385 for argument in &request.arguments {
2386 validate_protocol_v2_argument(argument)?;
2387 let mut payload = argument.clone();
2388 payload.push(b'\n');
2389 frames.push(PktLineFrame::data(payload)?);
2390 }
2391 }
2392 frames.push(PktLineFrame::Flush);
2393 Ok(frames)
2394}
2395
2396pub fn parse_protocol_v2_request(frames: &[PktLineFrame]) -> Result<ProtocolV2Request> {
2397 if matches!(frames, [PktLineFrame::Flush]) {
2398 return Ok(ProtocolV2Request::Done);
2399 }
2400 parse_protocol_v2_command_request(frames).map(ProtocolV2Request::Command)
2401}
2402
2403pub fn encode_protocol_v2_request(request: &ProtocolV2Request) -> Result<Vec<PktLineFrame>> {
2404 match request {
2405 ProtocolV2Request::Command(command) => encode_protocol_v2_command_request(command),
2406 ProtocolV2Request::Done => Ok(vec![PktLineFrame::Flush]),
2407 }
2408}
2409
2410pub fn read_protocol_v2_request(reader: &mut impl Read) -> Result<ProtocolV2Request> {
2411 let frames = read_pkt_line_frames_until_flush(reader)?;
2412 parse_protocol_v2_request(&frames)
2413}
2414
2415pub fn write_protocol_v2_request(
2416 writer: &mut impl Write,
2417 request: &ProtocolV2Request,
2418) -> Result<()> {
2419 match request {
2420 ProtocolV2Request::Command(command) => write_protocol_v2_command_request(writer, command),
2421 ProtocolV2Request::Done => {
2422 writer.write_all(b"0000")?;
2423 Ok(())
2424 }
2425 }
2426}
2427
2428pub fn read_protocol_v2_command_request(
2429 reader: &mut impl Read,
2430) -> Result<ProtocolV2CommandRequest> {
2431 let frames = read_pkt_line_frames_until_flush(reader)?;
2432 parse_protocol_v2_command_request(&frames)
2433}
2434
2435pub fn write_protocol_v2_command_request(
2436 writer: &mut impl Write,
2437 request: &ProtocolV2CommandRequest,
2438) -> Result<()> {
2439 validate_capability_name(&request.command)?;
2440 write_pkt_line_payload(
2441 writer,
2442 &line_from_str(&format!("command={}", request.command)),
2443 )?;
2444 for capability in &request.capabilities {
2445 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2446 }
2447 if !request.arguments.is_empty() {
2448 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2449 for argument in &request.arguments {
2450 validate_protocol_v2_argument(argument)?;
2451 let mut payload = argument.clone();
2452 payload.push(b'\n');
2453 write_pkt_line_payload(writer, &payload)?;
2454 }
2455 }
2456 writer.write_all(b"0000")?;
2457 Ok(())
2458}
2459
2460pub fn read_protocol_v2_ls_refs_request(reader: &mut impl Read) -> Result<ProtocolV2LsRefsRequest> {
2461 let request = read_protocol_v2_command_request(reader)?;
2462 ProtocolV2LsRefsRequest::from_command_request(&request)
2463}
2464
2465pub fn write_protocol_v2_ls_refs_request(
2466 writer: &mut impl Write,
2467 request: &ProtocolV2LsRefsRequest,
2468) -> Result<()> {
2469 let command = request.to_command_request()?;
2470 write_protocol_v2_command_request(writer, &command)
2471}
2472
2473pub fn parse_protocol_v2_ls_refs_response(
2474 format: ObjectFormat,
2475 frames: &[PktLineFrame],
2476) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2477 let mut records = Vec::new();
2478 let mut saw_flush = false;
2479 for (idx, frame) in frames.iter().enumerate() {
2480 match frame {
2481 PktLineFrame::Data(payload) => {
2482 if saw_flush {
2483 return Err(GitError::InvalidFormat(
2484 "ls-refs response has data after flush".into(),
2485 ));
2486 }
2487 records.push(parse_protocol_v2_ls_refs_line(format, payload)?);
2488 }
2489 PktLineFrame::Flush => {
2490 saw_flush = true;
2491 if !flush_terminates_protocol_v2_response(frames, idx) {
2492 return Err(GitError::InvalidFormat(
2493 "ls-refs response has frames after flush".into(),
2494 ));
2495 }
2496 }
2497 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2498 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2499 return Err(GitError::InvalidFormat(
2500 "ls-refs response contains a non-flush control packet".into(),
2501 ));
2502 }
2503 }
2504 }
2505 if !saw_flush {
2506 return Err(GitError::InvalidFormat(
2507 "ls-refs response missing flush".into(),
2508 ));
2509 }
2510 Ok(records)
2511}
2512
2513pub fn encode_protocol_v2_ls_refs_response(
2514 records: &[ProtocolV2LsRefsRecord],
2515) -> Result<Vec<PktLineFrame>> {
2516 let mut frames = Vec::new();
2517 for record in records {
2518 frames.push(PktLineFrame::data(line_from_str(
2519 &format_protocol_v2_ls_refs_record(record)?,
2520 ))?);
2521 }
2522 frames.push(PktLineFrame::Flush);
2523 Ok(frames)
2524}
2525
2526pub fn read_protocol_v2_ls_refs_response(
2527 format: ObjectFormat,
2528 reader: &mut impl Read,
2529) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2530 let frames = read_pkt_line_frames_until_flush(reader)?;
2531 parse_protocol_v2_ls_refs_response(format, &frames)
2532}
2533
2534pub fn write_protocol_v2_ls_refs_response(
2535 writer: &mut impl Write,
2536 records: &[ProtocolV2LsRefsRecord],
2537) -> Result<()> {
2538 for record in records {
2539 write_pkt_line_payload(
2540 writer,
2541 &line_from_str(&format_protocol_v2_ls_refs_record(record)?),
2542 )?;
2543 }
2544 writer.write_all(b"0000")?;
2545 Ok(())
2546}
2547
2548pub fn read_protocol_v2_ls_refs_response_until_response_end(
2549 format: ObjectFormat,
2550 reader: &mut impl Read,
2551) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2552 let frames = read_pkt_line_frames_until_response_end(reader)?;
2553 parse_protocol_v2_ls_refs_response(format, &frames)
2554}
2555
2556pub fn write_protocol_v2_ls_refs_response_with_response_end(
2557 writer: &mut impl Write,
2558 records: &[ProtocolV2LsRefsRecord],
2559) -> Result<()> {
2560 write_protocol_v2_ls_refs_response(writer, records)?;
2561 writer.write_all(b"0002")?;
2562 Ok(())
2563}
2564
2565pub fn exchange_protocol_v2_ls_refs(
2566 format: ObjectFormat,
2567 reader: &mut impl Read,
2568 writer: &mut impl Write,
2569 request: &ProtocolV2LsRefsRequest,
2570) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2571 write_protocol_v2_ls_refs_request(writer, request)?;
2572 writer.flush()?;
2573 read_protocol_v2_ls_refs_response(format, reader)
2574}
2575
2576pub fn protocol_v2_ls_refs_records_to_ref_advertisement_set(
2592 records: &[ProtocolV2LsRefsRecord],
2593) -> Result<RefAdvertisementSet> {
2594 let mut refs: Vec<RefAdvertisement> = Vec::new();
2595 let mut symrefs: Vec<Capability> = Vec::new();
2596 for record in records {
2597 match record {
2598 ProtocolV2LsRefsRecord::Ref(reference) => {
2599 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
2600 refs.push(RefAdvertisement {
2601 oid: reference.oid,
2602 name: reference.name.clone(),
2603 capabilities: Vec::new(),
2604 });
2605 if let Some(peeled) = &reference.peeled {
2606 refs.push(RefAdvertisement {
2607 oid: peeled.clone(),
2608 name: format!("{}^{{}}", reference.name),
2609 capabilities: Vec::new(),
2610 });
2611 }
2612 if let Some(target) = &reference.symref_target {
2613 symrefs.push(protocol_v2_symref_capability(&reference.name, target)?);
2614 }
2615 }
2616 ProtocolV2LsRefsRecord::Unborn {
2617 name,
2618 symref_target,
2619 ..
2620 } => {
2621 validate_protocol_v2_token("ls-refs ref name", name)?;
2622 if let Some(target) = symref_target {
2623 symrefs.push(protocol_v2_symref_capability(name, target)?);
2624 }
2625 }
2626 }
2627 }
2628 if !symrefs.is_empty() {
2629 if let Some(first) = refs.first_mut() {
2630 first.capabilities = symrefs;
2631 } else {
2632 return Err(GitError::InvalidFormat(
2633 "ls-refs response advertised symrefs without any concrete refs".into(),
2634 ));
2635 }
2636 }
2637 Ok(RefAdvertisementSet {
2638 protocol: ProtocolVersion::V2,
2639 refs,
2640 shallow: Vec::new(),
2641 })
2642}
2643
2644pub fn parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2649 format: ObjectFormat,
2650 frames: &[PktLineFrame],
2651) -> Result<RefAdvertisementSet> {
2652 let records = parse_protocol_v2_ls_refs_response(format, frames)?;
2653 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2654}
2655
2656pub fn read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2659 format: ObjectFormat,
2660 reader: &mut impl Read,
2661) -> Result<RefAdvertisementSet> {
2662 let records = read_protocol_v2_ls_refs_response(format, reader)?;
2663 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2664}
2665
2666fn protocol_v2_symref_capability(name: &str, target: &str) -> Result<Capability> {
2667 validate_protocol_v2_token("ls-refs symref-target", target)?;
2668 Ok(Capability {
2669 name: "symref".into(),
2670 value: Some(format!("{name}:{target}")),
2671 })
2672}
2673
2674pub fn read_protocol_v2_fetch_request(
2675 format: ObjectFormat,
2676 reader: &mut impl Read,
2677) -> Result<ProtocolV2FetchRequest> {
2678 let request = read_protocol_v2_command_request(reader)?;
2679 ProtocolV2FetchRequest::from_command_request(format, &request)
2680}
2681
2682pub fn write_protocol_v2_fetch_request(
2683 writer: &mut impl Write,
2684 request: &ProtocolV2FetchRequest,
2685) -> Result<()> {
2686 let command = request.to_command_request()?;
2687 write_protocol_v2_command_request(writer, &command)
2688}
2689
2690pub fn read_protocol_v2_object_info_request(
2691 format: ObjectFormat,
2692 reader: &mut impl Read,
2693) -> Result<ProtocolV2ObjectInfoRequest> {
2694 let request = read_protocol_v2_command_request(reader)?;
2695 ProtocolV2ObjectInfoRequest::from_command_request(format, &request)
2696}
2697
2698pub fn write_protocol_v2_object_info_request(
2699 writer: &mut impl Write,
2700 request: &ProtocolV2ObjectInfoRequest,
2701) -> Result<()> {
2702 let command = request.to_command_request()?;
2703 write_protocol_v2_command_request(writer, &command)
2704}
2705
2706pub fn parse_protocol_v2_fetch_response(
2707 format: ObjectFormat,
2708 frames: &[PktLineFrame],
2709) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2710 let mut sections = Vec::new();
2711 let mut current: Option<(String, Vec<Vec<u8>>)> = None;
2712 let mut saw_flush = false;
2713 for (idx, frame) in frames.iter().enumerate() {
2714 match frame {
2715 PktLineFrame::Data(payload) => {
2716 if saw_flush {
2717 return Err(GitError::InvalidFormat(
2718 "fetch response has data after flush".into(),
2719 ));
2720 }
2721 if let Some((_name, lines)) = &mut current {
2722 lines.push(payload.clone());
2723 } else {
2724 let name = parse_fetch_section_header(payload)?;
2725 current = Some((name, Vec::new()));
2726 }
2727 }
2728 PktLineFrame::Delimiter => {
2729 if saw_flush {
2730 return Err(GitError::InvalidFormat(
2731 "fetch response has delimiter after flush".into(),
2732 ));
2733 }
2734 let Some((name, lines)) = current.take() else {
2735 return Err(GitError::InvalidFormat(
2736 "fetch response has delimiter before section".into(),
2737 ));
2738 };
2739 sections.push(parse_fetch_section(format, name, lines)?);
2740 }
2741 PktLineFrame::Flush => {
2742 saw_flush = true;
2743 if !flush_terminates_protocol_v2_response(frames, idx) {
2744 return Err(GitError::InvalidFormat(
2745 "fetch response has frames after flush".into(),
2746 ));
2747 }
2748 if let Some((name, lines)) = current.take() {
2749 sections.push(parse_fetch_section(format, name, lines)?);
2750 }
2751 }
2752 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2753 PktLineFrame::ResponseEnd => {
2754 return Err(GitError::InvalidFormat(
2755 "fetch response contains response-end".into(),
2756 ));
2757 }
2758 }
2759 }
2760 if !saw_flush {
2761 return Err(GitError::InvalidFormat(
2762 "fetch response missing flush".into(),
2763 ));
2764 }
2765 Ok(sections)
2766}
2767
2768pub fn encode_protocol_v2_fetch_response(
2769 sections: &[ProtocolV2FetchResponseSection],
2770) -> Result<Vec<PktLineFrame>> {
2771 let mut frames = Vec::new();
2772 for (idx, section) in sections.iter().enumerate() {
2773 if idx != 0 {
2774 frames.push(PktLineFrame::Delimiter);
2775 }
2776 frames.push(PktLineFrame::data(line_from_str(
2777 protocol_v2_fetch_section_name(section),
2778 ))?);
2779 for line in format_protocol_v2_fetch_section_lines(section)? {
2780 frames.push(PktLineFrame::data(line)?);
2781 }
2782 }
2783 frames.push(PktLineFrame::Flush);
2784 Ok(frames)
2785}
2786
2787pub fn parse_protocol_v2_fetch_sideband_all_response(
2788 format: ObjectFormat,
2789 frames: &[PktLineFrame],
2790) -> Result<ProtocolV2FetchSidebandAllResponse> {
2791 let mut demuxed = Vec::new();
2792 let mut progress = Vec::new();
2793 let mut in_packfile = false;
2794 for frame in frames {
2795 match frame {
2796 PktLineFrame::Data(payload) if in_packfile => {
2797 demuxed.push(PktLineFrame::Data(payload.clone()));
2798 }
2799 PktLineFrame::Data(payload) => {
2800 let packet = parse_sideband_packet(payload)?;
2801 match packet.channel {
2802 SideBandChannel::Data => {
2803 if trim_trailing_lf(&packet.data) == b"packfile" {
2804 in_packfile = true;
2805 }
2806 demuxed.push(PktLineFrame::Data(packet.data));
2807 }
2808 SideBandChannel::Progress => progress.push(packet.data),
2809 SideBandChannel::Fatal => {
2810 let message = String::from_utf8_lossy(&packet.data).into_owned();
2811 return Err(GitError::InvalidFormat(format!(
2812 "sideband fatal: {message}"
2813 )));
2814 }
2815 }
2816 }
2817 PktLineFrame::Delimiter => {
2818 in_packfile = false;
2819 demuxed.push(PktLineFrame::Delimiter);
2820 }
2821 PktLineFrame::Flush => {
2822 in_packfile = false;
2823 demuxed.push(PktLineFrame::Flush);
2824 }
2825 PktLineFrame::ResponseEnd => {
2826 in_packfile = false;
2827 demuxed.push(PktLineFrame::ResponseEnd);
2828 }
2829 }
2830 }
2831 Ok(ProtocolV2FetchSidebandAllResponse {
2832 sections: parse_protocol_v2_fetch_response(format, &demuxed)?,
2833 progress,
2834 })
2835}
2836
2837pub fn encode_protocol_v2_fetch_sideband_all_response(
2838 sections: &[ProtocolV2FetchResponseSection],
2839) -> Result<Vec<PktLineFrame>> {
2840 let frames = encode_protocol_v2_fetch_response(sections)?;
2841 let mut encoded = Vec::new();
2842 let mut in_packfile = false;
2843 for frame in frames {
2844 match frame {
2845 PktLineFrame::Data(payload) if in_packfile => {
2846 encoded.push(PktLineFrame::Data(payload));
2847 }
2848 PktLineFrame::Data(payload) => {
2849 if trim_trailing_lf(&payload) == b"packfile" {
2850 in_packfile = true;
2851 }
2852 encoded.push(PktLineFrame::data(encode_sideband_packet(
2853 &SideBandPacket {
2854 channel: SideBandChannel::Data,
2855 data: payload,
2856 },
2857 )?)?);
2858 }
2859 PktLineFrame::Delimiter => {
2860 in_packfile = false;
2861 encoded.push(PktLineFrame::Delimiter);
2862 }
2863 PktLineFrame::Flush => {
2864 in_packfile = false;
2865 encoded.push(PktLineFrame::Flush);
2866 }
2867 PktLineFrame::ResponseEnd => {
2868 in_packfile = false;
2869 encoded.push(PktLineFrame::ResponseEnd);
2870 }
2871 }
2872 }
2873 Ok(encoded)
2874}
2875
2876pub fn read_protocol_v2_fetch_response(
2877 format: ObjectFormat,
2878 reader: &mut impl Read,
2879) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2880 let frames = read_pkt_line_frames_until_flush(reader)?;
2881 parse_protocol_v2_fetch_response(format, &frames)
2882}
2883
2884pub fn write_protocol_v2_fetch_response(
2885 writer: &mut impl Write,
2886 sections: &[ProtocolV2FetchResponseSection],
2887) -> Result<()> {
2888 write_protocol_v2_fetch_response_inner(writer, sections, false, false)
2889}
2890
2891pub fn read_protocol_v2_fetch_sideband_all_response(
2892 format: ObjectFormat,
2893 reader: &mut impl Read,
2894) -> Result<ProtocolV2FetchSidebandAllResponse> {
2895 let frames = read_pkt_line_frames_until_flush(reader)?;
2896 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2897}
2898
2899pub fn write_protocol_v2_fetch_sideband_all_response(
2900 writer: &mut impl Write,
2901 sections: &[ProtocolV2FetchResponseSection],
2902) -> Result<()> {
2903 write_protocol_v2_fetch_response_inner(writer, sections, true, false)
2904}
2905
2906pub fn read_protocol_v2_fetch_response_until_response_end(
2907 format: ObjectFormat,
2908 reader: &mut impl Read,
2909) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2910 let frames = read_pkt_line_frames_until_response_end(reader)?;
2911 parse_protocol_v2_fetch_response(format, &frames)
2912}
2913
2914pub fn write_protocol_v2_fetch_response_with_response_end(
2915 writer: &mut impl Write,
2916 sections: &[ProtocolV2FetchResponseSection],
2917) -> Result<()> {
2918 write_protocol_v2_fetch_response_inner(writer, sections, false, true)
2919}
2920
2921pub fn read_protocol_v2_fetch_sideband_all_response_until_response_end(
2922 format: ObjectFormat,
2923 reader: &mut impl Read,
2924) -> Result<ProtocolV2FetchSidebandAllResponse> {
2925 let frames = read_pkt_line_frames_until_response_end(reader)?;
2926 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2927}
2928
2929pub fn write_protocol_v2_fetch_sideband_all_response_with_response_end(
2930 writer: &mut impl Write,
2931 sections: &[ProtocolV2FetchResponseSection],
2932) -> Result<()> {
2933 write_protocol_v2_fetch_response_inner(writer, sections, true, true)
2934}
2935
2936fn write_protocol_v2_fetch_response_inner(
2937 writer: &mut impl Write,
2938 sections: &[ProtocolV2FetchResponseSection],
2939 sideband_all: bool,
2940 response_end: bool,
2941) -> Result<()> {
2942 let mut in_packfile = false;
2943 for (idx, section) in sections.iter().enumerate() {
2944 if idx != 0 {
2945 in_packfile = false;
2946 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2947 }
2948 write_protocol_v2_fetch_payload(
2949 writer,
2950 &line_from_str(protocol_v2_fetch_section_name(section)),
2951 sideband_all,
2952 &mut in_packfile,
2953 )?;
2954 for payload in format_protocol_v2_fetch_section_lines(section)? {
2955 write_protocol_v2_fetch_payload(writer, &payload, sideband_all, &mut in_packfile)?;
2956 }
2957 }
2958 writer.write_all(b"0000")?;
2959 if response_end {
2960 writer.write_all(b"0002")?;
2961 }
2962 Ok(())
2963}
2964
2965fn write_protocol_v2_fetch_payload(
2966 writer: &mut impl Write,
2967 payload: &[u8],
2968 sideband_all: bool,
2969 in_packfile: &mut bool,
2970) -> Result<()> {
2971 if sideband_all && !*in_packfile {
2972 if trim_trailing_lf(payload) == b"packfile" {
2973 *in_packfile = true;
2974 }
2975 write_sideband_payload(writer, SideBandChannel::Data, payload)
2976 } else {
2977 write_pkt_line_payload(writer, payload)
2978 }
2979}
2980
2981pub fn exchange_protocol_v2_fetch(
2982 format: ObjectFormat,
2983 reader: &mut impl Read,
2984 writer: &mut impl Write,
2985 request: &ProtocolV2FetchRequest,
2986) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2987 write_protocol_v2_fetch_request(writer, request)?;
2988 writer.flush()?;
2989 read_protocol_v2_fetch_response(format, reader)
2990}
2991
2992pub fn parse_protocol_v2_object_info_response(
2993 format: ObjectFormat,
2994 frames: &[PktLineFrame],
2995) -> Result<ProtocolV2ObjectInfoResponse> {
2996 let Some((first, rest)) = frames.split_first() else {
2997 return Err(GitError::InvalidFormat(
2998 "object-info response is empty".into(),
2999 ));
3000 };
3001 let PktLineFrame::Data(attrs) = first else {
3002 return Err(GitError::InvalidFormat(
3003 "object-info response must start with attributes".into(),
3004 ));
3005 };
3006 let attrs = parse_protocol_v2_line_text("object-info response attributes", attrs)?;
3007 let mut response = ProtocolV2ObjectInfoResponse::default();
3008 for attr in attrs.split(' ') {
3009 validate_protocol_v2_token("object-info response attribute", attr)?;
3010 match attr {
3011 "size" => {
3012 if response.size {
3013 return Err(GitError::InvalidFormat(
3014 "object-info response has duplicate size attribute".into(),
3015 ));
3016 }
3017 response.size = true;
3018 }
3019 other => {
3020 return Err(GitError::InvalidFormat(format!(
3021 "unsupported object-info response attribute {other}"
3022 )));
3023 }
3024 }
3025 }
3026 if !response.size {
3027 return Err(GitError::InvalidFormat(
3028 "object-info response is missing size attribute".into(),
3029 ));
3030 }
3031
3032 let mut saw_flush = false;
3033 for (idx, frame) in rest.iter().enumerate() {
3034 match frame {
3035 PktLineFrame::Data(payload) if !saw_flush => {
3036 response
3037 .records
3038 .push(parse_protocol_v2_object_info_record(format, payload)?);
3039 }
3040 PktLineFrame::Data(_) => {
3041 return Err(GitError::InvalidFormat(
3042 "object-info response has data after flush".into(),
3043 ));
3044 }
3045 PktLineFrame::Flush => {
3046 saw_flush = true;
3047 if idx + 1 != rest.len() {
3048 return Err(GitError::InvalidFormat(
3049 "object-info response has frames after flush".into(),
3050 ));
3051 }
3052 }
3053 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3054 return Err(GitError::InvalidFormat(
3055 "object-info response contains a non-flush control packet".into(),
3056 ));
3057 }
3058 }
3059 }
3060 if !saw_flush {
3061 return Err(GitError::InvalidFormat(
3062 "object-info response missing flush".into(),
3063 ));
3064 }
3065 Ok(response)
3066}
3067
3068pub fn encode_protocol_v2_object_info_response(
3069 response: &ProtocolV2ObjectInfoResponse,
3070) -> Result<Vec<PktLineFrame>> {
3071 if !response.size {
3072 return Err(GitError::InvalidFormat(
3073 "object-info response is missing size attribute".into(),
3074 ));
3075 }
3076 let mut frames = Vec::new();
3077 frames.push(PktLineFrame::data(line_from_str("size"))?);
3078 for record in &response.records {
3079 frames.push(PktLineFrame::data(line_from_str(&format!(
3080 "{} {}",
3081 record.oid, record.size
3082 )))?);
3083 }
3084 frames.push(PktLineFrame::Flush);
3085 Ok(frames)
3086}
3087
3088pub fn read_protocol_v2_object_info_response(
3089 format: ObjectFormat,
3090 reader: &mut impl Read,
3091) -> Result<ProtocolV2ObjectInfoResponse> {
3092 let frames = read_pkt_line_frames_until_flush(reader)?;
3093 parse_protocol_v2_object_info_response(format, &frames)
3094}
3095
3096pub fn write_protocol_v2_object_info_response(
3097 writer: &mut impl Write,
3098 response: &ProtocolV2ObjectInfoResponse,
3099) -> Result<()> {
3100 if !response.size {
3101 return Err(GitError::InvalidFormat(
3102 "object-info response is missing size attribute".into(),
3103 ));
3104 }
3105 write_pkt_line_payload(writer, b"size\n")?;
3106 for record in &response.records {
3107 write_pkt_line_payload(
3108 writer,
3109 &line_from_str(&format!("{} {}", record.oid, record.size)),
3110 )?;
3111 }
3112 writer.write_all(b"0000")?;
3113 Ok(())
3114}
3115
3116pub fn exchange_protocol_v2_object_info(
3117 format: ObjectFormat,
3118 reader: &mut impl Read,
3119 writer: &mut impl Write,
3120 request: &ProtocolV2ObjectInfoRequest,
3121) -> Result<ProtocolV2ObjectInfoResponse> {
3122 write_protocol_v2_object_info_request(writer, request)?;
3123 writer.flush()?;
3124 read_protocol_v2_object_info_response(format, reader)
3125}
3126
3127pub fn demux_protocol_v2_fetch_packfile(
3128 sections: &[ProtocolV2FetchResponseSection],
3129) -> Result<Option<SideBandDemux>> {
3130 let mut packfile = None;
3131 for section in sections {
3132 if let ProtocolV2FetchResponseSection::Packfile(lines) = section {
3133 if packfile.is_some() {
3134 return Err(GitError::InvalidFormat(
3135 "fetch response has duplicate packfile sections".into(),
3136 ));
3137 }
3138 packfile = Some(parse_and_demux_sideband_packets(lines)?);
3139 }
3140 }
3141 Ok(packfile)
3142}
3143
3144pub fn protocol_v2_object_format(capabilities: &[Capability]) -> Result<ObjectFormat> {
3145 let mut format = None;
3146 for capability in capabilities {
3147 if capability.name != "object-format" {
3148 continue;
3149 }
3150 if format.is_some() {
3151 return Err(GitError::InvalidFormat(
3152 "protocol v2 has duplicate object-format capabilities".into(),
3153 ));
3154 }
3155 let Some(value) = &capability.value else {
3156 return Err(GitError::InvalidFormat(
3157 "protocol v2 object-format capability is missing a value".into(),
3158 ));
3159 };
3160 format = Some(value.parse::<ObjectFormat>()?);
3161 }
3162 Ok(format.unwrap_or(ObjectFormat::Sha1))
3163}
3164
3165pub fn validate_protocol_v2_command_request_capabilities(
3166 handshake: &TransportHandshake,
3167 request: &ProtocolV2CommandRequest,
3168) -> Result<()> {
3169 if handshake.protocol != ProtocolVersion::V2 {
3170 return Err(GitError::InvalidFormat(
3171 "protocol v2 command validation requires a v2 handshake".into(),
3172 ));
3173 }
3174 let advertised =
3175 protocol_v2_capability(&handshake.capabilities, &request.command).ok_or_else(|| {
3176 GitError::InvalidFormat(format!("unadvertised command {}", request.command))
3177 })?;
3178 if advertised.name.is_empty() {
3179 return Err(GitError::InvalidFormat(
3180 "advertised command capability is empty".into(),
3181 ));
3182 }
3183 parse_protocol_v2_command_options(&request.capabilities)?;
3184
3185 for capability in &request.capabilities {
3186 let advertised = protocol_v2_capability(&handshake.capabilities, &capability.name)
3187 .ok_or_else(|| {
3188 GitError::InvalidFormat(format!(
3189 "unadvertised protocol v2 capability {}",
3190 capability.name
3191 ))
3192 })?;
3193 if capability.name == "object-format" {
3194 validate_protocol_v2_object_format_request(advertised, capability)?;
3195 }
3196 }
3197 Ok(())
3198}
3199
3200pub fn parse_protocol_v2_command_options(
3201 capabilities: &[Capability],
3202) -> Result<ProtocolV2CommandOptions> {
3203 let mut out = ProtocolV2CommandOptions::default();
3204 for capability in capabilities {
3205 match capability.name.as_str() {
3206 "agent" => {
3207 if out.agent.is_some() {
3208 return Err(GitError::InvalidFormat(
3209 "protocol v2 command has duplicate agent capabilities".into(),
3210 ));
3211 }
3212 let Some(value) = &capability.value else {
3213 return Err(GitError::InvalidFormat(
3214 "protocol v2 agent capability is missing a value".into(),
3215 ));
3216 };
3217 validate_protocol_v2_capability_value(value)?;
3218 out.agent = Some(value.clone());
3219 }
3220 "object-format" => {
3221 if out.object_format.is_some() {
3222 return Err(GitError::InvalidFormat(
3223 "protocol v2 command has duplicate object-format capabilities".into(),
3224 ));
3225 }
3226 let Some(value) = &capability.value else {
3227 return Err(GitError::InvalidFormat(
3228 "protocol v2 object-format capability is missing a value".into(),
3229 ));
3230 };
3231 out.object_format = Some(value.parse::<ObjectFormat>()?);
3232 }
3233 "server-option" => {
3234 let Some(value) = &capability.value else {
3235 return Err(GitError::InvalidFormat(
3236 "protocol v2 server-option capability is missing a value".into(),
3237 ));
3238 };
3239 validate_protocol_v2_capability_value(value)?;
3240 out.server_options.push(value.clone());
3241 }
3242 _ => out.extra.push(capability.clone()),
3243 }
3244 }
3245 Ok(out)
3246}
3247
3248pub fn encode_protocol_v2_command_options(
3249 options: &ProtocolV2CommandOptions,
3250) -> Result<Vec<Capability>> {
3251 let mut capabilities = Vec::new();
3252 if let Some(agent) = &options.agent {
3253 validate_protocol_v2_capability_value(agent)?;
3254 capabilities.push(Capability {
3255 name: "agent".into(),
3256 value: Some(agent.clone()),
3257 });
3258 }
3259 if let Some(format) = options.object_format {
3260 capabilities.push(Capability {
3261 name: "object-format".into(),
3262 value: Some(format.name().into()),
3263 });
3264 }
3265 for option in &options.server_options {
3266 validate_protocol_v2_capability_value(option)?;
3267 capabilities.push(Capability {
3268 name: "server-option".into(),
3269 value: Some(option.clone()),
3270 });
3271 }
3272 for capability in &options.extra {
3273 if matches!(
3274 capability.name.as_str(),
3275 "agent" | "object-format" | "server-option"
3276 ) {
3277 return Err(GitError::InvalidFormat(format!(
3278 "protocol v2 extra capability duplicates known capability {}",
3279 capability.name
3280 )));
3281 }
3282 encode_protocol_v2_capability(capability)?;
3283 capabilities.push(capability.clone());
3284 }
3285 Ok(capabilities)
3286}
3287
3288pub fn parse_protocol_v2_ls_refs_features(
3289 capabilities: &[Capability],
3290) -> Result<Option<ProtocolV2LsRefsFeatures>> {
3291 let mut ls_refs = None;
3292 for capability in capabilities {
3293 if capability.name != "ls-refs" {
3294 continue;
3295 }
3296 if ls_refs.is_some() {
3297 return Err(GitError::InvalidFormat(
3298 "protocol v2 has duplicate ls-refs capabilities".into(),
3299 ));
3300 }
3301 ls_refs = Some(parse_protocol_v2_ls_refs_feature_value(
3302 capability.value.as_deref(),
3303 )?);
3304 }
3305 Ok(ls_refs)
3306}
3307
3308pub fn encode_protocol_v2_ls_refs_capability(
3309 features: &ProtocolV2LsRefsFeatures,
3310) -> Result<Capability> {
3311 let mut values = Vec::new();
3312 if features.unborn {
3313 values.push("unborn".to_string());
3314 }
3315 for feature in &features.unknown {
3316 validate_protocol_v2_token("ls-refs feature", feature)?;
3317 if feature == "unborn" {
3318 return Err(GitError::InvalidFormat(
3319 "ls-refs unknown features must not duplicate known feature unborn".into(),
3320 ));
3321 }
3322 values.push(feature.clone());
3323 }
3324 Ok(Capability {
3325 name: "ls-refs".into(),
3326 value: (!values.is_empty()).then(|| values.join(" ")),
3327 })
3328}
3329
3330pub fn validate_protocol_v2_ls_refs_request_features(
3331 features: &ProtocolV2LsRefsFeatures,
3332 request: &ProtocolV2LsRefsRequest,
3333) -> Result<()> {
3334 if request.unborn && !features.unborn {
3335 return Err(GitError::InvalidFormat(
3336 "ls-refs request uses unborn without advertised unborn feature".into(),
3337 ));
3338 }
3339 Ok(())
3340}
3341
3342pub fn validate_protocol_v2_ls_refs_command_request(
3343 handshake: &TransportHandshake,
3344 request: &ProtocolV2CommandRequest,
3345) -> Result<ProtocolV2LsRefsRequest> {
3346 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3347 let ls_refs = ProtocolV2LsRefsRequest::from_command_request(request)?;
3348 let features = parse_protocol_v2_ls_refs_features(&handshake.capabilities)?
3349 .ok_or_else(|| GitError::InvalidFormat("ls-refs command was not advertised".into()))?;
3350 validate_protocol_v2_ls_refs_request_features(&features, &ls_refs)?;
3351 Ok(ls_refs)
3352}
3353
3354pub fn parse_protocol_v2_fetch_features(
3355 capabilities: &[Capability],
3356) -> Result<Option<ProtocolV2FetchFeatures>> {
3357 let mut fetch = None;
3358 for capability in capabilities {
3359 if capability.name != "fetch" {
3360 continue;
3361 }
3362 if fetch.is_some() {
3363 return Err(GitError::InvalidFormat(
3364 "protocol v2 has duplicate fetch capabilities".into(),
3365 ));
3366 }
3367 fetch = Some(parse_protocol_v2_fetch_feature_value(
3368 capability.value.as_deref(),
3369 )?);
3370 }
3371 Ok(fetch)
3372}
3373
3374pub fn encode_protocol_v2_fetch_capability(
3375 features: &ProtocolV2FetchFeatures,
3376) -> Result<Capability> {
3377 let mut values = Vec::new();
3378 if features.shallow {
3379 values.push("shallow".to_string());
3380 }
3381 if features.wait_for_done {
3382 values.push("wait-for-done".to_string());
3383 }
3384 if features.filter {
3385 values.push("filter".to_string());
3386 }
3387 if features.ref_in_want {
3388 values.push("ref-in-want".to_string());
3389 }
3390 if features.sideband_all {
3391 values.push("sideband-all".to_string());
3392 }
3393 if features.packfile_uris {
3394 values.push("packfile-uris".to_string());
3395 }
3396 for feature in &features.unknown {
3397 validate_protocol_v2_token("fetch feature", feature)?;
3398 if matches!(
3399 feature.as_str(),
3400 "shallow"
3401 | "wait-for-done"
3402 | "filter"
3403 | "ref-in-want"
3404 | "sideband-all"
3405 | "packfile-uris"
3406 ) {
3407 return Err(GitError::InvalidFormat(format!(
3408 "fetch unknown features must not duplicate known feature {feature}"
3409 )));
3410 }
3411 values.push(feature.clone());
3412 }
3413 Ok(Capability {
3414 name: "fetch".into(),
3415 value: (!values.is_empty()).then(|| values.join(" ")),
3416 })
3417}
3418
3419pub fn validate_protocol_v2_fetch_request_features(
3420 features: &ProtocolV2FetchFeatures,
3421 request: &ProtocolV2FetchRequest,
3422) -> Result<()> {
3423 if !features.shallow
3424 && (!request.shallow.is_empty()
3425 || request.deepen.is_some()
3426 || request.deepen_since.is_some()
3427 || !request.deepen_not.is_empty()
3428 || request.deepen_relative)
3429 {
3430 return Err(GitError::InvalidFormat(
3431 "fetch request uses shallow/deepen arguments without advertised shallow feature".into(),
3432 ));
3433 }
3434 if !features.filter && request.filter.is_some() {
3435 return Err(GitError::InvalidFormat(
3436 "fetch request uses filter without advertised filter feature".into(),
3437 ));
3438 }
3439 if !features.ref_in_want && !request.want_refs.is_empty() {
3440 return Err(GitError::InvalidFormat(
3441 "fetch request uses want-ref without advertised ref-in-want feature".into(),
3442 ));
3443 }
3444 if !features.sideband_all && request.sideband_all {
3445 return Err(GitError::InvalidFormat(
3446 "fetch request uses sideband-all without advertised sideband-all feature".into(),
3447 ));
3448 }
3449 if !features.packfile_uris && request.packfile_uris.is_some() {
3450 return Err(GitError::InvalidFormat(
3451 "fetch request uses packfile-uris without advertised packfile-uris feature".into(),
3452 ));
3453 }
3454 if !features.wait_for_done && request.wait_for_done {
3455 return Err(GitError::InvalidFormat(
3456 "fetch request uses wait-for-done without advertised wait-for-done feature".into(),
3457 ));
3458 }
3459 Ok(())
3460}
3461
3462pub fn validate_protocol_v2_fetch_command_request(
3463 handshake: &TransportHandshake,
3464 format: ObjectFormat,
3465 request: &ProtocolV2CommandRequest,
3466) -> Result<ProtocolV2FetchRequest> {
3467 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3468 let fetch = ProtocolV2FetchRequest::from_command_request(format, request)?;
3469 let features = parse_protocol_v2_fetch_features(&handshake.capabilities)?
3470 .ok_or_else(|| GitError::InvalidFormat("fetch command was not advertised".into()))?;
3471 validate_protocol_v2_fetch_request_features(&features, &fetch)?;
3472 Ok(fetch)
3473}
3474
3475pub fn validate_protocol_v2_object_info_command_request(
3476 handshake: &TransportHandshake,
3477 format: ObjectFormat,
3478 request: &ProtocolV2CommandRequest,
3479) -> Result<ProtocolV2ObjectInfoRequest> {
3480 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3481 let object_info = ProtocolV2ObjectInfoRequest::from_command_request(format, request)?;
3482 protocol_v2_capability(&handshake.capabilities, "object-info")
3483 .ok_or_else(|| GitError::InvalidFormat("object-info command was not advertised".into()))?;
3484 Ok(object_info)
3485}
3486
3487pub fn classify_protocol_v2_command_request(
3488 handshake: &TransportHandshake,
3489 format: ObjectFormat,
3490 request: &ProtocolV2CommandRequest,
3491) -> Result<ProtocolV2Command> {
3492 match request.command.as_str() {
3493 "ls-refs" => validate_protocol_v2_ls_refs_command_request(handshake, request)
3494 .map(ProtocolV2Command::LsRefs),
3495 "fetch" => validate_protocol_v2_fetch_command_request(handshake, format, request)
3496 .map(ProtocolV2Command::Fetch),
3497 "object-info" => {
3498 validate_protocol_v2_object_info_command_request(handshake, format, request)
3499 .map(ProtocolV2Command::ObjectInfo)
3500 }
3501 _ => {
3502 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3503 Ok(ProtocolV2Command::Unknown(request.clone()))
3504 }
3505 }
3506}
3507
3508pub fn classify_protocol_v2_request(
3509 handshake: &TransportHandshake,
3510 format: ObjectFormat,
3511 request: &ProtocolV2Request,
3512) -> Result<ProtocolV2SessionRequest> {
3513 match request {
3514 ProtocolV2Request::Command(command) => {
3515 classify_protocol_v2_command_request(handshake, format, command)
3516 .map(ProtocolV2SessionRequest::Command)
3517 }
3518 ProtocolV2Request::Done => Ok(ProtocolV2SessionRequest::Done),
3519 }
3520}
3521
3522pub fn read_protocol_v2_session_request(
3523 handshake: &TransportHandshake,
3524 format: ObjectFormat,
3525 reader: &mut impl Read,
3526) -> Result<ProtocolV2SessionRequest> {
3527 let request = read_protocol_v2_request(reader)?;
3528 classify_protocol_v2_request(handshake, format, &request)
3529}
3530
3531fn protocol_v2_capability<'a>(
3532 capabilities: &'a [Capability],
3533 name: &str,
3534) -> Option<&'a Capability> {
3535 capabilities
3536 .iter()
3537 .find(|capability| capability.name == name)
3538}
3539
3540fn validate_protocol_v2_object_format_request(
3541 advertised: &Capability,
3542 requested: &Capability,
3543) -> Result<()> {
3544 let Some(advertised) = &advertised.value else {
3545 return Err(GitError::InvalidFormat(
3546 "advertised object-format capability is missing a value".into(),
3547 ));
3548 };
3549 let Some(requested) = &requested.value else {
3550 return Err(GitError::InvalidFormat(
3551 "requested object-format capability is missing a value".into(),
3552 ));
3553 };
3554 if advertised != requested {
3555 return Err(GitError::InvalidFormat(format!(
3556 "requested object-format {requested} does not match advertised {advertised}"
3557 )));
3558 }
3559 Ok(())
3560}
3561
3562fn parse_protocol_v2_ls_refs_feature_value(
3563 value: Option<&str>,
3564) -> Result<ProtocolV2LsRefsFeatures> {
3565 let mut out = ProtocolV2LsRefsFeatures::default();
3566 let Some(value) = value else {
3567 return Ok(out);
3568 };
3569 if value.is_empty() {
3570 return Err(GitError::InvalidFormat(
3571 "protocol v2 ls-refs capability value is empty".into(),
3572 ));
3573 }
3574 for feature in value.split(' ') {
3575 validate_protocol_v2_token("ls-refs feature", feature)?;
3576 match feature {
3577 "unborn" => out.unborn = true,
3578 other => out.unknown.push(other.to_string()),
3579 }
3580 }
3581 Ok(out)
3582}
3583
3584fn parse_protocol_v2_fetch_feature_value(value: Option<&str>) -> Result<ProtocolV2FetchFeatures> {
3585 let mut out = ProtocolV2FetchFeatures::default();
3586 let Some(value) = value else {
3587 return Ok(out);
3588 };
3589 if value.is_empty() {
3590 return Err(GitError::InvalidFormat(
3591 "protocol v2 fetch capability value is empty".into(),
3592 ));
3593 }
3594 for feature in value.split(' ') {
3595 validate_protocol_v2_token("fetch feature", feature)?;
3596 match feature {
3597 "shallow" => out.shallow = true,
3598 "wait-for-done" => out.wait_for_done = true,
3599 "filter" => out.filter = true,
3600 "ref-in-want" => out.ref_in_want = true,
3601 "sideband-all" => out.sideband_all = true,
3602 "packfile-uris" => out.packfile_uris = true,
3603 other => out.unknown.push(other.to_string()),
3604 }
3605 }
3606 Ok(out)
3607}
3608
3609pub fn parse_capabilities(input: &[u8]) -> Result<Vec<Capability>> {
3610 let input = trim_trailing_lf(input);
3611 if input.is_empty() {
3612 return Ok(Vec::new());
3613 }
3614 let text =
3615 std::str::from_utf8(input).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3616 text.split(' ')
3617 .map(parse_capability_token)
3618 .collect::<Result<Vec<_>>>()
3619}
3620
3621pub fn encode_capabilities(capabilities: &[Capability]) -> Result<Vec<u8>> {
3622 let mut out = Vec::new();
3623 for (idx, capability) in capabilities.iter().enumerate() {
3624 validate_capability_field("capability name", &capability.name)?;
3625 if idx != 0 {
3626 out.push(b' ');
3627 }
3628 out.extend_from_slice(capability.name.as_bytes());
3629 if let Some(value) = &capability.value {
3630 validate_capability_field("capability value", value)?;
3631 out.push(b'=');
3632 out.extend_from_slice(value.as_bytes());
3633 }
3634 }
3635 Ok(out)
3636}
3637
3638pub fn parse_ref_advertisement(format: ObjectFormat, payload: &[u8]) -> Result<RefAdvertisement> {
3639 let payload = trim_trailing_lf(payload);
3640 let (reference, capabilities) = match payload.iter().position(|byte| *byte == 0) {
3641 Some(idx) => (&payload[..idx], parse_capabilities(&payload[idx + 1..])?),
3642 None => (payload, Vec::new()),
3643 };
3644 let text =
3645 std::str::from_utf8(reference).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3646 let (oid, name) = text
3647 .split_once(' ')
3648 .ok_or_else(|| GitError::InvalidFormat("advertised ref is missing name".into()))?;
3649 if name.is_empty() {
3650 return Err(GitError::InvalidFormat(
3651 "advertised ref name is empty".into(),
3652 ));
3653 }
3654 Ok(RefAdvertisement {
3655 oid: ObjectId::from_hex(format, oid)?,
3656 name: name.to_string(),
3657 capabilities,
3658 })
3659}
3660
3661pub fn encode_ref_advertisement(advertisement: &RefAdvertisement) -> Result<Vec<u8>> {
3662 validate_protocol_v2_token("advertised ref name", &advertisement.name)?;
3663 let mut out = advertisement.oid.to_string().into_bytes();
3664 out.push(b' ');
3665 out.extend_from_slice(advertisement.name.as_bytes());
3666 if !advertisement.capabilities.is_empty() {
3667 out.push(0);
3668 out.extend_from_slice(&encode_capabilities(&advertisement.capabilities)?);
3669 }
3670 out.push(b'\n');
3671 Ok(out)
3672}
3673
3674pub fn parse_ref_advertisements(
3675 format: ObjectFormat,
3676 frames: &[PktLineFrame],
3677) -> Result<Vec<RefAdvertisement>> {
3678 Ok(parse_ref_advertisement_set(format, frames)?.refs)
3679}
3680
3681pub fn parse_ref_advertisement_set(
3682 format: ObjectFormat,
3683 frames: &[PktLineFrame],
3684) -> Result<RefAdvertisementSet> {
3685 let mut set = RefAdvertisementSet {
3686 protocol: ProtocolVersion::V0,
3687 refs: Vec::new(),
3688 shallow: Vec::new(),
3689 };
3690 let mut saw_flush = false;
3691 let mut in_shallow = false;
3692 for (idx, frame) in frames.iter().enumerate() {
3693 match frame {
3694 PktLineFrame::Data(payload) if !saw_flush => {
3695 let trimmed = trim_trailing_lf(payload);
3696 if trimmed == b"version 1" {
3697 if idx != 0 {
3698 return Err(GitError::InvalidFormat(
3699 "advertised ref protocol version must be the first line".into(),
3700 ));
3701 }
3702 set.protocol = ProtocolVersion::V1;
3703 continue;
3704 }
3705 if trimmed.starts_with(b"version ") {
3706 return Err(GitError::InvalidFormat(
3707 "unsupported advertised ref protocol version".into(),
3708 ));
3709 }
3710 if trimmed.starts_with(b"shallow ") {
3711 if set.refs.is_empty() {
3712 return Err(GitError::InvalidFormat(
3713 "advertised shallow refs must follow advertised refs".into(),
3714 ));
3715 }
3716 let text = parse_protocol_v2_line_text("advertised shallow ref", payload)?;
3717 set.shallow.push(parse_oid_argument(
3718 format,
3719 "advertised shallow ref",
3720 text,
3721 "shallow ",
3722 )?);
3723 in_shallow = true;
3724 continue;
3725 }
3726 if in_shallow {
3727 return Err(GitError::InvalidFormat(
3728 "advertised refs must not follow shallow refs".into(),
3729 ));
3730 }
3731 let advertisement = parse_ref_advertisement(format, payload)?;
3732 if !set.refs.is_empty() && !advertisement.capabilities.is_empty() {
3733 return Err(GitError::InvalidFormat(
3734 "advertised ref capabilities must appear on the first ref".into(),
3735 ));
3736 }
3737 set.refs.push(advertisement);
3738 }
3739 PktLineFrame::Data(_) => {
3740 return Err(GitError::InvalidFormat(
3741 "advertised ref stream has data after flush".into(),
3742 ));
3743 }
3744 PktLineFrame::Flush => {
3745 saw_flush = true;
3746 if idx + 1 != frames.len() {
3747 return Err(GitError::InvalidFormat(
3748 "advertised ref stream has frames after flush".into(),
3749 ));
3750 }
3751 }
3752 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3753 return Err(GitError::InvalidFormat(
3754 "advertised ref stream contains a non-flush control packet".into(),
3755 ));
3756 }
3757 }
3758 }
3759 if !saw_flush {
3760 return Err(GitError::InvalidFormat(
3761 "advertised ref stream missing flush".into(),
3762 ));
3763 }
3764 Ok(set)
3765}
3766
3767pub fn encode_ref_advertisements(advertisements: &[RefAdvertisement]) -> Result<Vec<PktLineFrame>> {
3768 encode_ref_advertisement_set(&RefAdvertisementSet {
3769 protocol: ProtocolVersion::V0,
3770 refs: advertisements.to_vec(),
3771 shallow: Vec::new(),
3772 })
3773}
3774
3775pub fn encode_ref_advertisement_set(set: &RefAdvertisementSet) -> Result<Vec<PktLineFrame>> {
3776 let mut frames = Vec::new();
3777 match set.protocol {
3778 ProtocolVersion::V0 => {}
3779 ProtocolVersion::V1 => frames.push(PktLineFrame::data(line_from_str("version 1"))?),
3780 ProtocolVersion::V2 => {
3781 return Err(GitError::InvalidFormat(
3782 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3783 ));
3784 }
3785 }
3786 if set.refs.is_empty() && !set.shallow.is_empty() {
3787 return Err(GitError::InvalidFormat(
3788 "advertised shallow refs require advertised refs".into(),
3789 ));
3790 }
3791 for (idx, advertisement) in set.refs.iter().enumerate() {
3792 if idx != 0 && !advertisement.capabilities.is_empty() {
3793 return Err(GitError::InvalidFormat(
3794 "advertised ref capabilities must appear on the first ref".into(),
3795 ));
3796 }
3797 frames.push(PktLineFrame::data(encode_ref_advertisement(
3798 advertisement,
3799 )?)?);
3800 }
3801 for oid in &set.shallow {
3802 frames.push(PktLineFrame::data(line_from_str(&format!(
3803 "shallow {oid}"
3804 )))?);
3805 }
3806 frames.push(PktLineFrame::Flush);
3807 Ok(frames)
3808}
3809
3810pub fn read_ref_advertisements(
3811 format: ObjectFormat,
3812 reader: &mut impl Read,
3813) -> Result<Vec<RefAdvertisement>> {
3814 let frames = read_pkt_line_frames_until_flush(reader)?;
3815 parse_ref_advertisements(format, &frames)
3816}
3817
3818pub fn read_ref_advertisement_set(
3819 format: ObjectFormat,
3820 reader: &mut impl Read,
3821) -> Result<RefAdvertisementSet> {
3822 let frames = read_pkt_line_frames_until_flush(reader)?;
3823 parse_ref_advertisement_set(format, &frames)
3824}
3825
3826pub fn write_ref_advertisements(
3827 writer: &mut impl Write,
3828 advertisements: &[RefAdvertisement],
3829) -> Result<()> {
3830 write_ref_advertisement_stream(writer, ProtocolVersion::V0, advertisements, &[])
3831}
3832
3833pub fn write_ref_advertisement_set(
3834 writer: &mut impl Write,
3835 set: &RefAdvertisementSet,
3836) -> Result<()> {
3837 write_ref_advertisement_stream(writer, set.protocol, &set.refs, &set.shallow)
3838}
3839
3840fn write_ref_advertisement_stream(
3841 writer: &mut impl Write,
3842 protocol: ProtocolVersion,
3843 refs: &[RefAdvertisement],
3844 shallow: &[ObjectId],
3845) -> Result<()> {
3846 match protocol {
3847 ProtocolVersion::V0 => {}
3848 ProtocolVersion::V1 => write_pkt_line_payload(writer, b"version 1\n")?,
3849 ProtocolVersion::V2 => {
3850 return Err(GitError::InvalidFormat(
3851 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3852 ));
3853 }
3854 }
3855 if refs.is_empty() && !shallow.is_empty() {
3856 return Err(GitError::InvalidFormat(
3857 "advertised shallow refs require advertised refs".into(),
3858 ));
3859 }
3860 for (idx, advertisement) in refs.iter().enumerate() {
3861 if idx != 0 && !advertisement.capabilities.is_empty() {
3862 return Err(GitError::InvalidFormat(
3863 "advertised ref capabilities must appear on the first ref".into(),
3864 ));
3865 }
3866 write_pkt_line_payload(writer, &encode_ref_advertisement(advertisement)?)?;
3867 }
3868 for oid in shallow {
3869 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
3870 }
3871 writer.write_all(b"0000")?;
3872 Ok(())
3873}
3874
3875pub fn parse_dumb_http_info_refs(
3876 format: ObjectFormat,
3877 input: &[u8],
3878) -> Result<Vec<DumbHttpRefRecord>> {
3879 if input.is_empty() {
3880 return Ok(Vec::new());
3881 }
3882 input
3883 .split_inclusive(|byte| *byte == b'\n')
3884 .map(|line| parse_dumb_http_info_ref_record(format, line))
3885 .collect()
3886}
3887
3888pub fn encode_dumb_http_info_refs(records: &[DumbHttpRefRecord]) -> Result<Vec<u8>> {
3889 let mut out = Vec::new();
3890 for record in records {
3891 validate_dumb_http_ref_name(&record.name)?;
3892 out.extend_from_slice(record.oid.to_string().as_bytes());
3893 out.push(b'\t');
3894 out.extend_from_slice(record.name.as_bytes());
3895 if record.peeled {
3896 out.extend_from_slice(b"^{}");
3897 }
3898 out.push(b'\n');
3899 }
3900 Ok(out)
3901}
3902
3903pub fn read_dumb_http_info_refs(
3904 format: ObjectFormat,
3905 reader: &mut impl Read,
3906) -> Result<Vec<DumbHttpRefRecord>> {
3907 let mut input = Vec::new();
3908 reader.read_to_end(&mut input)?;
3909 parse_dumb_http_info_refs(format, &input)
3910}
3911
3912pub fn write_dumb_http_info_refs(
3913 writer: &mut impl Write,
3914 records: &[DumbHttpRefRecord],
3915) -> Result<()> {
3916 for record in records {
3917 validate_dumb_http_ref_name(&record.name)?;
3918 writer.write_all(record.oid.to_string().as_bytes())?;
3919 writer.write_all(b"\t")?;
3920 writer.write_all(record.name.as_bytes())?;
3921 if record.peeled {
3922 writer.write_all(b"^{}")?;
3923 }
3924 writer.write_all(b"\n")?;
3925 }
3926 Ok(())
3927}
3928
3929pub fn parse_dumb_http_alternates(input: &[u8]) -> Result<Vec<String>> {
3930 if input.is_empty() {
3931 return Ok(Vec::new());
3932 }
3933 input
3934 .split_inclusive(|byte| *byte == b'\n')
3935 .map(parse_dumb_http_alternate)
3936 .collect()
3937}
3938
3939pub fn encode_dumb_http_alternates(alternates: &[String]) -> Result<Vec<u8>> {
3940 let mut out = Vec::new();
3941 for alternate in alternates {
3942 validate_dumb_http_alternate(alternate)?;
3943 out.extend_from_slice(alternate.as_bytes());
3944 out.push(b'\n');
3945 }
3946 Ok(out)
3947}
3948
3949pub fn read_dumb_http_alternates(reader: &mut impl Read) -> Result<Vec<String>> {
3950 let mut input = Vec::new();
3951 reader.read_to_end(&mut input)?;
3952 parse_dumb_http_alternates(&input)
3953}
3954
3955pub fn write_dumb_http_alternates(writer: &mut impl Write, alternates: &[String]) -> Result<()> {
3956 for alternate in alternates {
3957 validate_dumb_http_alternate(alternate)?;
3958 writer.write_all(alternate.as_bytes())?;
3959 writer.write_all(b"\n")?;
3960 }
3961 Ok(())
3962}
3963
3964pub fn parse_dumb_http_packs(
3965 format: ObjectFormat,
3966 input: &[u8],
3967) -> Result<Vec<DumbHttpPackRecord>> {
3968 if input.is_empty() {
3969 return Ok(Vec::new());
3970 }
3971 input
3972 .split_inclusive(|byte| *byte == b'\n')
3973 .map(|line| parse_dumb_http_pack_record(format, line))
3974 .collect()
3975}
3976
3977pub fn encode_dumb_http_packs(records: &[DumbHttpPackRecord]) -> Result<Vec<u8>> {
3978 let mut out = Vec::new();
3979 for record in records {
3980 out.extend_from_slice(format!("P pack-{}.pack\n", record.hash).as_bytes());
3981 }
3982 Ok(out)
3983}
3984
3985pub fn read_dumb_http_packs(
3986 format: ObjectFormat,
3987 reader: &mut impl Read,
3988) -> Result<Vec<DumbHttpPackRecord>> {
3989 let mut input = Vec::new();
3990 reader.read_to_end(&mut input)?;
3991 parse_dumb_http_packs(format, &input)
3992}
3993
3994pub fn write_dumb_http_packs(
3995 writer: &mut impl Write,
3996 records: &[DumbHttpPackRecord],
3997) -> Result<()> {
3998 for record in records {
3999 writer.write_all(format!("P pack-{}.pack\n", record.hash).as_bytes())?;
4000 }
4001 Ok(())
4002}
4003
4004pub fn parse_upload_pack_request(
4005 format: ObjectFormat,
4006 frames: &[PktLineFrame],
4007) -> Result<Option<UploadPackRequest>> {
4008 if matches!(frames, [PktLineFrame::Flush]) {
4009 return Ok(None);
4010 }
4011
4012 let mut request = UploadPackRequest::default();
4013 let mut in_options = false;
4014 let mut saw_flush = false;
4015 for (idx, frame) in frames.iter().enumerate() {
4016 match frame {
4017 PktLineFrame::Data(payload) if !saw_flush => {
4018 let text = parse_protocol_v2_line_text("upload-pack request line", payload)?;
4019 if let Some(value) = text.strip_prefix("want ") {
4020 if in_options {
4021 return Err(GitError::InvalidFormat(
4022 "upload-pack request has want after options".into(),
4023 ));
4024 }
4025 let (oid, capabilities) = if request.wants.is_empty() {
4026 value
4027 .split_once(' ')
4028 .map_or((value, None), |(oid, caps)| (oid, Some(caps.as_bytes())))
4029 } else {
4030 if value.contains(' ') {
4031 return Err(GitError::InvalidFormat(
4032 "additional upload-pack want has capabilities".into(),
4033 ));
4034 }
4035 (value, None)
4036 };
4037 validate_protocol_v2_token("upload-pack want", oid)?;
4038 request.wants.push(ObjectId::from_hex(format, oid)?);
4039 if let Some(capabilities) = capabilities {
4040 request.capabilities = parse_capabilities(capabilities)?;
4041 }
4042 continue;
4043 }
4044
4045 if request.wants.is_empty() {
4046 return Err(GitError::InvalidFormat(
4047 "upload-pack request must start with want".into(),
4048 ));
4049 }
4050 in_options = true;
4051 if text.starts_with("shallow ") {
4052 request.shallow.push(parse_oid_argument(
4053 format,
4054 "upload-pack shallow",
4055 text,
4056 "shallow ",
4057 )?);
4058 } else if text.starts_with("deepen ") {
4059 if request.deepen.is_some() {
4060 return Err(GitError::InvalidFormat(
4061 "upload-pack request has duplicate deepen".into(),
4062 ));
4063 }
4064 request.deepen =
4065 Some(parse_u32_argument("upload-pack deepen", text, "deepen ")?);
4066 } else if text.starts_with("deepen-since ") {
4067 if request.deepen_since.is_some() {
4068 return Err(GitError::InvalidFormat(
4069 "upload-pack request has duplicate deepen-since".into(),
4070 ));
4071 }
4072 request.deepen_since = Some(parse_u64_argument(
4073 "upload-pack deepen-since",
4074 text,
4075 "deepen-since ",
4076 )?);
4077 } else if let Some(name) = text.strip_prefix("deepen-not ") {
4078 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4079 request.deepen_not.push(name.to_string());
4080 } else if let Some(filter) = text.strip_prefix("filter ") {
4081 if request.filter.is_some() {
4082 return Err(GitError::InvalidFormat(
4083 "upload-pack request has duplicate filter".into(),
4084 ));
4085 }
4086 validate_protocol_v2_token("upload-pack filter", filter)?;
4087 request.filter = Some(filter.to_string());
4088 } else {
4089 return Err(GitError::InvalidFormat(format!(
4090 "unsupported upload-pack request line {text}"
4091 )));
4092 }
4093 }
4094 PktLineFrame::Data(_) => {
4095 return Err(GitError::InvalidFormat(
4096 "upload-pack request has data after flush".into(),
4097 ));
4098 }
4099 PktLineFrame::Flush => {
4100 saw_flush = true;
4101 if idx + 1 != frames.len() {
4102 return Err(GitError::InvalidFormat(
4103 "upload-pack request has frames after flush".into(),
4104 ));
4105 }
4106 }
4107 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4108 return Err(GitError::InvalidFormat(
4109 "upload-pack request contains a non-flush control packet".into(),
4110 ));
4111 }
4112 }
4113 }
4114 if !saw_flush {
4115 return Err(GitError::InvalidFormat(
4116 "upload-pack request missing flush".into(),
4117 ));
4118 }
4119 if request.wants.is_empty() {
4120 return Err(GitError::InvalidFormat(
4121 "upload-pack request missing want".into(),
4122 ));
4123 }
4124 Ok(Some(request))
4125}
4126
4127pub fn encode_upload_pack_request(
4128 request: Option<&UploadPackRequest>,
4129) -> Result<Vec<PktLineFrame>> {
4130 let Some(request) = request else {
4131 return Ok(vec![PktLineFrame::Flush]);
4132 };
4133 if request.wants.is_empty() {
4134 return Err(GitError::InvalidFormat(
4135 "upload-pack request missing want".into(),
4136 ));
4137 }
4138
4139 let mut frames = Vec::new();
4140 for (idx, oid) in request.wants.iter().enumerate() {
4141 let mut line = format!("want {oid}");
4142 if idx == 0 && !request.capabilities.is_empty() {
4143 line.push(' ');
4144 line.push_str(
4145 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4146 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4147 );
4148 }
4149 frames.push(PktLineFrame::data(line_from_str(&line))?);
4150 }
4151 for oid in &request.shallow {
4152 frames.push(PktLineFrame::data(line_from_str(&format!(
4153 "shallow {oid}"
4154 )))?);
4155 }
4156 if let Some(deepen) = request.deepen {
4157 if deepen == 0 {
4158 return Err(GitError::InvalidFormat(
4159 "upload-pack deepen must be positive".into(),
4160 ));
4161 }
4162 frames.push(PktLineFrame::data(line_from_str(&format!(
4163 "deepen {deepen}"
4164 )))?);
4165 }
4166 if let Some(deepen_since) = request.deepen_since {
4167 frames.push(PktLineFrame::data(line_from_str(&format!(
4168 "deepen-since {deepen_since}"
4169 )))?);
4170 }
4171 for name in &request.deepen_not {
4172 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4173 frames.push(PktLineFrame::data(line_from_str(&format!(
4174 "deepen-not {name}"
4175 )))?);
4176 }
4177 if let Some(filter) = &request.filter {
4178 validate_protocol_v2_token("upload-pack filter", filter)?;
4179 frames.push(PktLineFrame::data(line_from_str(&format!(
4180 "filter {filter}"
4181 )))?);
4182 }
4183 frames.push(PktLineFrame::Flush);
4184 Ok(frames)
4185}
4186
4187pub fn read_upload_pack_request(
4188 format: ObjectFormat,
4189 reader: &mut impl Read,
4190) -> Result<Option<UploadPackRequest>> {
4191 let frames = read_pkt_line_frames_until_flush(reader)?;
4192 parse_upload_pack_request(format, &frames)
4193}
4194
4195pub fn write_upload_pack_request(
4196 writer: &mut impl Write,
4197 request: Option<&UploadPackRequest>,
4198) -> Result<()> {
4199 let Some(request) = request else {
4200 writer.write_all(b"0000")?;
4201 return Ok(());
4202 };
4203 if request.wants.is_empty() {
4204 return Err(GitError::InvalidFormat(
4205 "upload-pack request missing want".into(),
4206 ));
4207 }
4208
4209 for (idx, oid) in request.wants.iter().enumerate() {
4210 let mut line = format!("want {oid}");
4211 if idx == 0 && !request.capabilities.is_empty() {
4212 line.push(' ');
4213 line.push_str(
4214 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4215 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4216 );
4217 }
4218 write_pkt_line_payload(writer, &line_from_str(&line))?;
4219 }
4220 for oid in &request.shallow {
4221 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
4222 }
4223 if let Some(deepen) = request.deepen {
4224 if deepen == 0 {
4225 return Err(GitError::InvalidFormat(
4226 "upload-pack deepen must be positive".into(),
4227 ));
4228 }
4229 write_pkt_line_payload(writer, &line_from_str(&format!("deepen {deepen}")))?;
4230 }
4231 if let Some(deepen_since) = request.deepen_since {
4232 write_pkt_line_payload(
4233 writer,
4234 &line_from_str(&format!("deepen-since {deepen_since}")),
4235 )?;
4236 }
4237 for name in &request.deepen_not {
4238 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4239 write_pkt_line_payload(writer, &line_from_str(&format!("deepen-not {name}")))?;
4240 }
4241 if let Some(filter) = &request.filter {
4242 validate_protocol_v2_token("upload-pack filter", filter)?;
4243 write_pkt_line_payload(writer, &line_from_str(&format!("filter {filter}")))?;
4244 }
4245 writer.write_all(b"0000")?;
4246 Ok(())
4247}
4248
4249pub fn parse_upload_pack_features(capabilities: &[Capability]) -> Result<UploadPackFeatures> {
4250 let mut features = UploadPackFeatures::default();
4251 for capability in capabilities {
4252 match capability.name.as_str() {
4253 "multi_ack" => set_upload_pack_flag(&mut features.multi_ack, capability)?,
4254 "multi_ack_detailed" => {
4255 set_upload_pack_flag(&mut features.multi_ack_detailed, capability)?
4256 }
4257 "no-done" => set_upload_pack_flag(&mut features.no_done, capability)?,
4258 "thin-pack" => set_upload_pack_flag(&mut features.thin_pack, capability)?,
4259 "side-band" => set_upload_pack_flag(&mut features.side_band, capability)?,
4260 "side-band-64k" => set_upload_pack_flag(&mut features.side_band_64k, capability)?,
4261 "ofs-delta" => set_upload_pack_flag(&mut features.ofs_delta, capability)?,
4262 "shallow" => set_upload_pack_flag(&mut features.shallow, capability)?,
4263 "deepen-since" => set_upload_pack_flag(&mut features.deepen_since, capability)?,
4264 "deepen-not" => set_upload_pack_flag(&mut features.deepen_not, capability)?,
4265 "include-tag" => set_upload_pack_flag(&mut features.include_tag, capability)?,
4266 "no-progress" => set_upload_pack_flag(&mut features.no_progress, capability)?,
4267 "allow-tip-sha1-in-want" => {
4268 set_upload_pack_flag(&mut features.allow_tip_sha1_in_want, capability)?
4269 }
4270 "allow-reachable-sha1-in-want" => {
4271 set_upload_pack_flag(&mut features.allow_reachable_sha1_in_want, capability)?
4272 }
4273 "filter" => set_upload_pack_flag(&mut features.filter, capability)?,
4274 "agent" => {
4275 let Some(agent) = &capability.value else {
4276 return Err(GitError::InvalidFormat(
4277 "upload-pack agent capability is missing value".into(),
4278 ));
4279 };
4280 if features.agent.is_some() {
4281 return Err(GitError::InvalidFormat(
4282 "upload-pack has duplicate agent capability".into(),
4283 ));
4284 }
4285 validate_capability_field("upload-pack agent", agent)?;
4286 features.agent = Some(agent.clone());
4287 }
4288 "object-format" => {
4289 let Some(format) = &capability.value else {
4290 return Err(GitError::InvalidFormat(
4291 "upload-pack object-format capability is missing value".into(),
4292 ));
4293 };
4294 if features.object_format.is_some() {
4295 return Err(GitError::InvalidFormat(
4296 "upload-pack has duplicate object-format capability".into(),
4297 ));
4298 }
4299 validate_capability_field("upload-pack object-format", format)?;
4300 features.object_format = Some(format.parse()?);
4301 }
4302 "symref" => {
4303 let Some(symref) = &capability.value else {
4304 return Err(GitError::InvalidFormat(
4305 "upload-pack symref capability is missing value".into(),
4306 ));
4307 };
4308 validate_capability_field("upload-pack symref", symref)?;
4309 features.symrefs.push(symref.clone());
4310 }
4311 _ => {
4312 encode_capabilities(std::slice::from_ref(capability))?;
4313 if features
4314 .unknown
4315 .iter()
4316 .any(|known| known.name == capability.name)
4317 {
4318 return Err(GitError::InvalidFormat(format!(
4319 "upload-pack has duplicate {} capability",
4320 capability.name
4321 )));
4322 }
4323 features.unknown.push(capability.clone());
4324 }
4325 }
4326 }
4327 Ok(features)
4328}
4329
4330pub fn encode_upload_pack_features(features: &UploadPackFeatures) -> Result<Vec<Capability>> {
4331 let mut capabilities = Vec::new();
4332 push_upload_pack_flag(&mut capabilities, "multi_ack", features.multi_ack);
4333 push_upload_pack_flag(
4334 &mut capabilities,
4335 "multi_ack_detailed",
4336 features.multi_ack_detailed,
4337 );
4338 push_upload_pack_flag(&mut capabilities, "no-done", features.no_done);
4339 push_upload_pack_flag(&mut capabilities, "thin-pack", features.thin_pack);
4340 push_upload_pack_flag(&mut capabilities, "side-band", features.side_band);
4341 push_upload_pack_flag(&mut capabilities, "side-band-64k", features.side_band_64k);
4342 push_upload_pack_flag(&mut capabilities, "ofs-delta", features.ofs_delta);
4343 push_upload_pack_flag(&mut capabilities, "shallow", features.shallow);
4344 push_upload_pack_flag(&mut capabilities, "deepen-since", features.deepen_since);
4345 push_upload_pack_flag(&mut capabilities, "deepen-not", features.deepen_not);
4346 push_upload_pack_flag(&mut capabilities, "include-tag", features.include_tag);
4347 push_upload_pack_flag(&mut capabilities, "no-progress", features.no_progress);
4348 push_upload_pack_flag(
4349 &mut capabilities,
4350 "allow-tip-sha1-in-want",
4351 features.allow_tip_sha1_in_want,
4352 );
4353 push_upload_pack_flag(
4354 &mut capabilities,
4355 "allow-reachable-sha1-in-want",
4356 features.allow_reachable_sha1_in_want,
4357 );
4358 push_upload_pack_flag(&mut capabilities, "filter", features.filter);
4359 if let Some(agent) = &features.agent {
4360 validate_capability_field("upload-pack agent", agent)?;
4361 capabilities.push(Capability {
4362 name: "agent".into(),
4363 value: Some(agent.clone()),
4364 });
4365 }
4366 if let Some(format) = features.object_format {
4367 capabilities.push(Capability {
4368 name: "object-format".into(),
4369 value: Some(format.name().into()),
4370 });
4371 }
4372 for symref in &features.symrefs {
4373 validate_capability_field("upload-pack symref", symref)?;
4374 capabilities.push(Capability {
4375 name: "symref".into(),
4376 value: Some(symref.clone()),
4377 });
4378 }
4379 for capability in &features.unknown {
4380 if is_known_upload_pack_capability(&capability.name) {
4381 return Err(GitError::InvalidFormat(format!(
4382 "upload-pack unknown capability duplicates known capability {}",
4383 capability.name
4384 )));
4385 }
4386 encode_capabilities(std::slice::from_ref(capability))?;
4387 capabilities.push(capability.clone());
4388 }
4389 Ok(capabilities)
4390}
4391
4392pub fn validate_upload_pack_request_features(
4393 features: &UploadPackFeatures,
4394 request: &UploadPackRequest,
4395) -> Result<()> {
4396 for capability in &request.capabilities {
4397 if is_upload_pack_flag_capability(&capability.name) {
4398 reject_capability_value("upload-pack request capability", capability)?;
4399 }
4400 match capability.name.as_str() {
4401 "multi_ack" if !features.multi_ack => {
4402 return Err(GitError::InvalidFormat(
4403 "upload-pack request uses multi_ack without advertised capability".into(),
4404 ));
4405 }
4406 "multi_ack_detailed" if !features.multi_ack_detailed => {
4407 return Err(GitError::InvalidFormat(
4408 "upload-pack request uses multi_ack_detailed without advertised capability"
4409 .into(),
4410 ));
4411 }
4412 "no-done" if !features.no_done => {
4413 return Err(GitError::InvalidFormat(
4414 "upload-pack request uses no-done without advertised capability".into(),
4415 ));
4416 }
4417 "thin-pack" if !features.thin_pack => {
4418 return Err(GitError::InvalidFormat(
4419 "upload-pack request uses thin-pack without advertised capability".into(),
4420 ));
4421 }
4422 "side-band" if !features.side_band => {
4423 return Err(GitError::InvalidFormat(
4424 "upload-pack request uses side-band without advertised capability".into(),
4425 ));
4426 }
4427 "side-band-64k" if !features.side_band_64k => {
4428 return Err(GitError::InvalidFormat(
4429 "upload-pack request uses side-band-64k without advertised capability".into(),
4430 ));
4431 }
4432 "ofs-delta" if !features.ofs_delta => {
4433 return Err(GitError::InvalidFormat(
4434 "upload-pack request uses ofs-delta without advertised capability".into(),
4435 ));
4436 }
4437 "include-tag" if !features.include_tag => {
4438 return Err(GitError::InvalidFormat(
4439 "upload-pack request uses include-tag without advertised capability".into(),
4440 ));
4441 }
4442 "no-progress" if !features.no_progress => {
4443 return Err(GitError::InvalidFormat(
4444 "upload-pack request uses no-progress without advertised capability".into(),
4445 ));
4446 }
4447 "allow-tip-sha1-in-want" if !features.allow_tip_sha1_in_want => {
4448 return Err(GitError::InvalidFormat(
4449 "upload-pack request uses allow-tip-sha1-in-want without advertised capability"
4450 .into(),
4451 ));
4452 }
4453 "allow-reachable-sha1-in-want" if !features.allow_reachable_sha1_in_want => {
4454 return Err(GitError::InvalidFormat(
4455 "upload-pack request uses allow-reachable-sha1-in-want without advertised capability"
4456 .into(),
4457 ));
4458 }
4459 "filter" if !features.filter => {
4460 return Err(GitError::InvalidFormat(
4461 "upload-pack request uses filter capability without advertised capability"
4462 .into(),
4463 ));
4464 }
4465 "agent" => {
4466 let Some(agent) = &capability.value else {
4467 return Err(GitError::InvalidFormat(
4468 "upload-pack request agent capability is missing value".into(),
4469 ));
4470 };
4471 validate_capability_field("upload-pack request agent", agent)?;
4472 }
4473 "object-format" => {
4474 let Some(format) = &capability.value else {
4475 return Err(GitError::InvalidFormat(
4476 "upload-pack request object-format capability is missing value".into(),
4477 ));
4478 };
4479 let requested_format: ObjectFormat = format.parse()?;
4480 if features.object_format != Some(requested_format) {
4481 return Err(GitError::InvalidFormat(
4482 "upload-pack request object-format was not advertised".into(),
4483 ));
4484 }
4485 }
4486 name if is_known_upload_pack_capability(name) => {}
4487 _ => {
4488 if !features
4489 .unknown
4490 .iter()
4491 .any(|advertised| advertised.name == capability.name)
4492 {
4493 return Err(GitError::InvalidFormat(format!(
4494 "upload-pack request uses unadvertised capability {}",
4495 capability.name
4496 )));
4497 }
4498 }
4499 }
4500 }
4501
4502 let sideband = request
4503 .capabilities
4504 .iter()
4505 .any(|capability| capability.name == "side-band");
4506 let sideband_64k = request
4507 .capabilities
4508 .iter()
4509 .any(|capability| capability.name == "side-band-64k");
4510 if sideband && sideband_64k {
4511 return Err(GitError::InvalidFormat(
4512 "upload-pack request must not request both side-band and side-band-64k".into(),
4513 ));
4514 }
4515
4516 if !features.shallow && (!request.shallow.is_empty() || request.deepen.is_some()) {
4517 return Err(GitError::InvalidFormat(
4518 "upload-pack request uses shallow/deepen without advertised shallow capability".into(),
4519 ));
4520 }
4521 if !features.deepen_since && request.deepen_since.is_some() {
4522 return Err(GitError::InvalidFormat(
4523 "upload-pack request uses deepen-since without advertised capability".into(),
4524 ));
4525 }
4526 if !features.deepen_not && !request.deepen_not.is_empty() {
4527 return Err(GitError::InvalidFormat(
4528 "upload-pack request uses deepen-not without advertised capability".into(),
4529 ));
4530 }
4531 if !features.filter && request.filter.is_some() {
4532 return Err(GitError::InvalidFormat(
4533 "upload-pack request uses filter without advertised capability".into(),
4534 ));
4535 }
4536 Ok(())
4537}
4538
4539pub fn build_upload_pack_raw_packfile_response<C, B>(
4540 features: &UploadPackFeatures,
4541 request: UploadPackRequest,
4542 haves: impl IntoIterator<Item = ObjectId>,
4543 mut contains_object: C,
4544 mut build_pack: B,
4545) -> Result<UploadPackRawPackfileResponse>
4546where
4547 C: FnMut(&ObjectId) -> Result<bool>,
4548 B: FnMut(Vec<ObjectId>, Vec<ObjectId>) -> Result<Option<Vec<u8>>>,
4549{
4550 validate_upload_pack_request_features(features, &request)?;
4551 for want in &request.wants {
4552 if !contains_object(want)? {
4553 return Err(GitError::InvalidObject(format!(
4554 "upload-pack requested missing object {want}"
4555 )));
4556 }
4557 }
4558 let known_haves = haves
4559 .into_iter()
4560 .filter_map(|oid| match contains_object(&oid) {
4561 Ok(true) => Some(Ok(oid)),
4562 Ok(false) => None,
4563 Err(err) => Some(Err(err)),
4564 })
4565 .collect::<Result<Vec<_>>>()?;
4566 let packfile = build_pack(request.wants, known_haves)?
4567 .ok_or_else(|| GitError::InvalidObject("upload-pack request produced empty pack".into()))?;
4568 Ok(UploadPackRawPackfileResponse {
4569 acknowledgments: vec![UploadPackAcknowledgment::Nak],
4570 packfile,
4571 })
4572}
4573
4574pub fn parse_upload_pack_shallow_update(
4575 format: ObjectFormat,
4576 frames: &[PktLineFrame],
4577) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4578 let mut entries = Vec::new();
4579 let mut saw_flush = false;
4580 for (idx, frame) in frames.iter().enumerate() {
4581 match frame {
4582 PktLineFrame::Data(payload) if !saw_flush => {
4583 entries.push(parse_fetch_shallow_info(format, payload)?);
4584 }
4585 PktLineFrame::Data(_) => {
4586 return Err(GitError::InvalidFormat(
4587 "upload-pack shallow update has data after flush".into(),
4588 ));
4589 }
4590 PktLineFrame::Flush => {
4591 saw_flush = true;
4592 if idx + 1 != frames.len() {
4593 return Err(GitError::InvalidFormat(
4594 "upload-pack shallow update has frames after flush".into(),
4595 ));
4596 }
4597 }
4598 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4599 return Err(GitError::InvalidFormat(
4600 "upload-pack shallow update contains a non-flush control packet".into(),
4601 ));
4602 }
4603 }
4604 }
4605 if !saw_flush {
4606 return Err(GitError::InvalidFormat(
4607 "upload-pack shallow update missing flush".into(),
4608 ));
4609 }
4610 Ok(entries)
4611}
4612
4613pub fn encode_upload_pack_shallow_update(
4614 entries: &[ProtocolV2FetchShallowInfo],
4615) -> Result<Vec<PktLineFrame>> {
4616 let mut frames = Vec::new();
4617 for entry in entries {
4618 let line = match entry {
4619 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4620 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4621 };
4622 frames.push(PktLineFrame::data(line_from_str(&line))?);
4623 }
4624 frames.push(PktLineFrame::Flush);
4625 Ok(frames)
4626}
4627
4628pub fn read_upload_pack_shallow_update(
4629 format: ObjectFormat,
4630 reader: &mut impl Read,
4631) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4632 let frames = read_pkt_line_frames_until_flush(reader)?;
4633 parse_upload_pack_shallow_update(format, &frames)
4634}
4635
4636pub fn write_upload_pack_shallow_update(
4637 writer: &mut impl Write,
4638 entries: &[ProtocolV2FetchShallowInfo],
4639) -> Result<()> {
4640 for entry in entries {
4641 let line = match entry {
4642 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4643 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4644 };
4645 write_pkt_line_payload(writer, &line_from_str(&line))?;
4646 }
4647 writer.write_all(b"0000")?;
4648 Ok(())
4649}
4650
4651pub fn parse_upload_pack_negotiation_request(
4652 format: ObjectFormat,
4653 frames: &[PktLineFrame],
4654) -> Result<UploadPackNegotiationRequest> {
4655 let mut request = UploadPackNegotiationRequest::default();
4656 let mut terminated = false;
4657 for (idx, frame) in frames.iter().enumerate() {
4658 match frame {
4659 PktLineFrame::Data(payload) if !terminated => {
4660 let text = parse_protocol_v2_line_text("upload-pack negotiation line", payload)?;
4661 if text == "done" {
4662 request.done = true;
4663 terminated = true;
4664 if idx + 1 != frames.len() {
4665 return Err(GitError::InvalidFormat(
4666 "upload-pack negotiation has frames after done".into(),
4667 ));
4668 }
4669 } else if text.starts_with("have ") {
4670 request.haves.push(parse_oid_argument(
4671 format,
4672 "upload-pack have",
4673 text,
4674 "have ",
4675 )?);
4676 } else {
4677 return Err(GitError::InvalidFormat(format!(
4678 "unsupported upload-pack negotiation line {text}"
4679 )));
4680 }
4681 }
4682 PktLineFrame::Data(_) => {
4683 return Err(GitError::InvalidFormat(
4684 "upload-pack negotiation has data after terminator".into(),
4685 ));
4686 }
4687 PktLineFrame::Flush => {
4688 terminated = true;
4689 if idx + 1 != frames.len() {
4690 return Err(GitError::InvalidFormat(
4691 "upload-pack negotiation has frames after flush".into(),
4692 ));
4693 }
4694 }
4695 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4696 return Err(GitError::InvalidFormat(
4697 "upload-pack negotiation contains a non-flush control packet".into(),
4698 ));
4699 }
4700 }
4701 }
4702 if !terminated {
4703 return Err(GitError::InvalidFormat(
4704 "upload-pack negotiation missing terminator".into(),
4705 ));
4706 }
4707 Ok(request)
4708}
4709
4710pub fn encode_upload_pack_negotiation_request(
4711 request: &UploadPackNegotiationRequest,
4712) -> Result<Vec<PktLineFrame>> {
4713 let mut frames = Vec::new();
4714 for oid in &request.haves {
4715 frames.push(PktLineFrame::data(line_from_str(&format!("have {oid}")))?);
4716 }
4717 if request.done {
4718 frames.push(PktLineFrame::data(line_from_str("done"))?);
4719 } else {
4720 frames.push(PktLineFrame::Flush);
4721 }
4722 Ok(frames)
4723}
4724
4725pub fn read_upload_pack_negotiation_request(
4726 format: ObjectFormat,
4727 reader: &mut impl Read,
4728) -> Result<UploadPackNegotiationRequest> {
4729 let mut frames = Vec::new();
4730 loop {
4731 let Some(frame) = read_pkt_line_frame(reader)? else {
4732 return Err(GitError::InvalidFormat(
4733 "pkt-line stream ended before upload-pack negotiation terminator".into(),
4734 ));
4735 };
4736 let done = match &frame {
4737 PktLineFrame::Flush => true,
4738 PktLineFrame::Data(payload) => trim_trailing_lf(payload) == b"done",
4739 _ => false,
4740 };
4741 frames.push(frame);
4742 if done {
4743 return parse_upload_pack_negotiation_request(format, &frames);
4744 }
4745 }
4746}
4747
4748pub fn write_upload_pack_negotiation_request(
4749 writer: &mut impl Write,
4750 request: &UploadPackNegotiationRequest,
4751) -> Result<()> {
4752 for oid in &request.haves {
4753 write_pkt_line_payload(writer, &line_from_str(&format!("have {oid}")))?;
4754 }
4755 if request.done {
4756 write_pkt_line_payload(writer, b"done\n")?;
4757 } else {
4758 writer.write_all(b"0000")?;
4759 }
4760 Ok(())
4761}
4762
4763pub fn parse_upload_pack_acknowledgment(
4764 format: ObjectFormat,
4765 payload: &[u8],
4766) -> Result<UploadPackAcknowledgment> {
4767 let text = parse_protocol_v2_line_text("upload-pack acknowledgment", payload)?;
4768 if text == "NAK" {
4769 return Ok(UploadPackAcknowledgment::Nak);
4770 }
4771 let Some(rest) = text.strip_prefix("ACK ") else {
4772 return Err(GitError::InvalidFormat(format!(
4773 "unsupported upload-pack acknowledgment {text}"
4774 )));
4775 };
4776 let mut fields = rest.split(' ');
4777 let oid = fields
4778 .next()
4779 .ok_or_else(|| GitError::InvalidFormat("upload-pack ACK missing object id".into()))?;
4780 validate_protocol_v2_token("upload-pack ACK", oid)?;
4781 let status = match fields.next() {
4782 None => None,
4783 Some("continue") => Some(UploadPackAckStatus::Continue),
4784 Some("common") => Some(UploadPackAckStatus::Common),
4785 Some("ready") => Some(UploadPackAckStatus::Ready),
4786 Some(other) => {
4787 return Err(GitError::InvalidFormat(format!(
4788 "unsupported upload-pack ACK status {other}"
4789 )));
4790 }
4791 };
4792 if fields.next().is_some() {
4793 return Err(GitError::InvalidFormat(
4794 "upload-pack ACK has too many fields".into(),
4795 ));
4796 }
4797 Ok(UploadPackAcknowledgment::Ack {
4798 oid: ObjectId::from_hex(format, oid)?,
4799 status,
4800 })
4801}
4802
4803pub fn encode_upload_pack_acknowledgment(
4804 acknowledgment: &UploadPackAcknowledgment,
4805) -> Result<Vec<u8>> {
4806 let line = match acknowledgment {
4807 UploadPackAcknowledgment::Nak => "NAK".to_string(),
4808 UploadPackAcknowledgment::Ack { oid, status } => {
4809 let mut line = format!("ACK {oid}");
4810 if let Some(status) = status {
4811 line.push(' ');
4812 line.push_str(match status {
4813 UploadPackAckStatus::Continue => "continue",
4814 UploadPackAckStatus::Common => "common",
4815 UploadPackAckStatus::Ready => "ready",
4816 });
4817 }
4818 line
4819 }
4820 };
4821 Ok(line_from_str(&line))
4822}
4823
4824pub fn read_upload_pack_acknowledgment(
4825 format: ObjectFormat,
4826 reader: &mut impl Read,
4827) -> Result<UploadPackAcknowledgment> {
4828 let Some(frame) = read_pkt_line_frame(reader)? else {
4829 return Err(GitError::InvalidFormat(
4830 "pkt-line stream ended before upload-pack acknowledgment".into(),
4831 ));
4832 };
4833 match frame {
4834 PktLineFrame::Data(payload) => parse_upload_pack_acknowledgment(format, &payload),
4835 _ => Err(GitError::InvalidFormat(
4836 "upload-pack acknowledgment must be a data packet".into(),
4837 )),
4838 }
4839}
4840
4841pub fn write_upload_pack_acknowledgment(
4842 writer: &mut impl Write,
4843 acknowledgment: &UploadPackAcknowledgment,
4844) -> Result<()> {
4845 write_pkt_line_payload(writer, &encode_upload_pack_acknowledgment(acknowledgment)?)
4846}
4847
4848pub fn parse_upload_pack_packfile_response(
4849 format: ObjectFormat,
4850 frames: &[PktLineFrame],
4851) -> Result<UploadPackPackfileResponse> {
4852 let mut response = UploadPackPackfileResponse::default();
4853 let mut in_sideband = false;
4854 let mut saw_flush = false;
4855 for (idx, frame) in frames.iter().enumerate() {
4856 match frame {
4857 PktLineFrame::Data(payload) if !saw_flush => {
4858 if !in_sideband
4859 && (trim_trailing_lf(payload) == b"NAK" || payload.starts_with(b"ACK "))
4860 {
4861 response
4862 .acknowledgments
4863 .push(parse_upload_pack_acknowledgment(format, payload)?);
4864 continue;
4865 }
4866 in_sideband = true;
4867 response.sideband.push(parse_sideband_packet(payload)?);
4868 }
4869 PktLineFrame::Data(_) => {
4870 return Err(GitError::InvalidFormat(
4871 "upload-pack packfile response has data after flush".into(),
4872 ));
4873 }
4874 PktLineFrame::Flush => {
4875 saw_flush = true;
4876 if idx + 1 != frames.len() {
4877 return Err(GitError::InvalidFormat(
4878 "upload-pack packfile response has frames after flush".into(),
4879 ));
4880 }
4881 }
4882 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4883 return Err(GitError::InvalidFormat(
4884 "upload-pack packfile response contains a non-flush control packet".into(),
4885 ));
4886 }
4887 }
4888 }
4889 if !saw_flush {
4890 return Err(GitError::InvalidFormat(
4891 "upload-pack packfile response missing flush".into(),
4892 ));
4893 }
4894 Ok(response)
4895}
4896
4897pub fn encode_upload_pack_packfile_response(
4898 response: &UploadPackPackfileResponse,
4899) -> Result<Vec<PktLineFrame>> {
4900 let mut frames = Vec::new();
4901 for acknowledgment in &response.acknowledgments {
4902 frames.push(PktLineFrame::data(encode_upload_pack_acknowledgment(
4903 acknowledgment,
4904 )?)?);
4905 }
4906 for packet in &response.sideband {
4907 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
4908 }
4909 frames.push(PktLineFrame::Flush);
4910 Ok(frames)
4911}
4912
4913pub fn read_upload_pack_packfile_response(
4914 format: ObjectFormat,
4915 reader: &mut impl Read,
4916) -> Result<UploadPackPackfileResponse> {
4917 let frames = read_pkt_line_frames_until_flush(reader)?;
4918 parse_upload_pack_packfile_response(format, &frames)
4919}
4920
4921pub fn write_upload_pack_packfile_response(
4922 writer: &mut impl Write,
4923 response: &UploadPackPackfileResponse,
4924) -> Result<()> {
4925 for acknowledgment in &response.acknowledgments {
4926 write_upload_pack_acknowledgment(writer, acknowledgment)?;
4927 }
4928 for packet in &response.sideband {
4929 write_sideband_packet(writer, packet)?;
4930 }
4931 writer.write_all(b"0000")?;
4932 Ok(())
4933}
4934
4935pub fn demux_upload_pack_packfile_response(
4936 response: &UploadPackPackfileResponse,
4937) -> Result<SideBandDemux> {
4938 demux_sideband_packets(&response.sideband)
4939}
4940
4941pub fn parse_upload_pack_raw_packfile_response(
4942 format: ObjectFormat,
4943 input: &[u8],
4944) -> Result<UploadPackRawPackfileResponse> {
4945 let mut response = UploadPackRawPackfileResponse::default();
4946 let mut offset = 0usize;
4947 while offset < input.len() {
4948 match PktLineFrame::parse(&input[offset..]) {
4949 Ok((PktLineFrame::Data(payload), consumed)) => {
4950 let trimmed = trim_trailing_lf(&payload);
4951 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
4952 response
4953 .acknowledgments
4954 .push(parse_upload_pack_acknowledgment(format, &payload)?);
4955 offset += consumed;
4956 continue;
4957 }
4958 return Err(GitError::InvalidFormat(
4959 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
4960 ));
4961 }
4962 Ok((PktLineFrame::Flush | PktLineFrame::Delimiter | PktLineFrame::ResponseEnd, _)) => {
4963 return Err(GitError::InvalidFormat(
4964 "upload-pack raw packfile response contains a control packet".into(),
4965 ));
4966 }
4967 Err(_) if input[offset..].starts_with(b"PACK") => break,
4968 Err(err) => return Err(err),
4969 }
4970 }
4971 response.packfile = input[offset..].to_vec();
4972 if response.packfile.is_empty() {
4973 return Err(GitError::InvalidFormat(
4974 "upload-pack raw packfile response missing packfile".into(),
4975 ));
4976 }
4977 if !response.packfile.starts_with(b"PACK") {
4978 return Err(GitError::InvalidFormat(
4979 "upload-pack raw packfile response packfile must start with PACK".into(),
4980 ));
4981 }
4982 Ok(response)
4983}
4984
4985pub fn encode_upload_pack_raw_packfile_response(
4986 response: &UploadPackRawPackfileResponse,
4987) -> Result<Vec<u8>> {
4988 if response.packfile.is_empty() {
4989 return Err(GitError::InvalidFormat(
4990 "upload-pack raw packfile response missing packfile".into(),
4991 ));
4992 }
4993 if !response.packfile.starts_with(b"PACK") {
4994 return Err(GitError::InvalidFormat(
4995 "upload-pack raw packfile response packfile must start with PACK".into(),
4996 ));
4997 }
4998 let mut out = Vec::new();
4999 for acknowledgment in &response.acknowledgments {
5000 write_pkt_line_payload(
5001 &mut out,
5002 &encode_upload_pack_acknowledgment(acknowledgment)?,
5003 )?;
5004 }
5005 out.extend_from_slice(&response.packfile);
5006 Ok(out)
5007}
5008
5009pub fn read_upload_pack_raw_packfile_response(
5010 format: ObjectFormat,
5011 reader: &mut impl Read,
5012) -> Result<UploadPackRawPackfileResponse> {
5013 let mut input = Vec::new();
5014 reader.read_to_end(&mut input)?;
5015 parse_upload_pack_raw_packfile_response(format, &input)
5016}
5017
5018pub fn write_upload_pack_raw_packfile_response(
5019 writer: &mut impl Write,
5020 response: &UploadPackRawPackfileResponse,
5021) -> Result<()> {
5022 if response.packfile.is_empty() {
5023 return Err(GitError::InvalidFormat(
5024 "upload-pack raw packfile response missing packfile".into(),
5025 ));
5026 }
5027 if !response.packfile.starts_with(b"PACK") {
5028 return Err(GitError::InvalidFormat(
5029 "upload-pack raw packfile response packfile must start with PACK".into(),
5030 ));
5031 }
5032 for acknowledgment in &response.acknowledgments {
5033 write_upload_pack_acknowledgment(writer, acknowledgment)?;
5034 }
5035 writer.write_all(&response.packfile)?;
5036 Ok(())
5037}
5038
5039pub fn parse_upload_pack_shallow_info_section(
5050 format: ObjectFormat,
5051 input: &[u8],
5052) -> Result<(Vec<ProtocolV2FetchShallowInfo>, usize)> {
5053 let mut entries = Vec::new();
5054 let mut offset = 0usize;
5055 loop {
5056 let (frame, consumed) = PktLineFrame::parse(&input[offset..])?;
5057 offset += consumed;
5058 match frame {
5059 PktLineFrame::Data(payload) => {
5060 entries.push(parse_fetch_shallow_info(format, &payload)?)
5061 }
5062 PktLineFrame::Flush => return Ok((entries, offset)),
5063 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5064 return Err(GitError::InvalidFormat(
5065 "upload-pack shallow-info section contains a non-flush control packet".into(),
5066 ));
5067 }
5068 }
5069 }
5070}
5071
5072pub fn parse_upload_pack_shallow_info_and_raw_packfile_response(
5083 format: ObjectFormat,
5084 input: &[u8],
5085) -> Result<(
5086 Vec<ProtocolV2FetchShallowInfo>,
5087 UploadPackRawPackfileResponse,
5088)> {
5089 let (shallow, consumed) = parse_upload_pack_shallow_info_section(format, input)?;
5090 let response = parse_upload_pack_raw_packfile_response(format, &input[consumed..])?;
5091 Ok((shallow, response))
5092}
5093
5094pub fn read_upload_pack_shallow_info_and_raw_packfile_response(
5102 format: ObjectFormat,
5103 reader: &mut impl Read,
5104) -> Result<(
5105 Vec<ProtocolV2FetchShallowInfo>,
5106 UploadPackRawPackfileResponse,
5107)> {
5108 let mut input = Vec::new();
5109 reader.read_to_end(&mut input)?;
5110 parse_upload_pack_shallow_info_and_raw_packfile_response(format, &input)
5111}
5112
5113pub fn parse_receive_pack_request(
5114 format: ObjectFormat,
5115 frames: &[PktLineFrame],
5116) -> Result<ReceivePackRequest> {
5117 let mut request = ReceivePackRequest::default();
5118 let mut saw_command = false;
5119 let mut saw_flush = false;
5120 for (idx, frame) in frames.iter().enumerate() {
5121 match frame {
5122 PktLineFrame::Data(payload) if !saw_flush => {
5123 let payload = trim_trailing_lf(payload);
5124 if payload.is_empty() {
5125 return Err(GitError::InvalidFormat(
5126 "receive-pack request line is empty".into(),
5127 ));
5128 }
5129 if let Some(shallow) = payload.strip_prefix(b"shallow ") {
5130 if saw_command {
5131 return Err(GitError::InvalidFormat(
5132 "receive-pack request has shallow after commands".into(),
5133 ));
5134 }
5135 let shallow = std::str::from_utf8(shallow)
5136 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
5137 validate_protocol_v2_token("receive-pack shallow", shallow)?;
5138 request.shallow.push(ObjectId::from_hex(format, shallow)?);
5139 continue;
5140 }
5141
5142 let (command, capabilities) = match payload.iter().position(|byte| *byte == 0) {
5143 Some(nul) if !saw_command => (
5144 &payload[..nul],
5145 Some(parse_capabilities(&payload[nul + 1..])?),
5146 ),
5147 Some(_) => {
5148 return Err(GitError::InvalidFormat(
5149 "receive-pack capabilities must appear on the first command".into(),
5150 ));
5151 }
5152 None => (payload, None),
5153 };
5154 let command = parse_receive_pack_command(format, command)?;
5155 if let Some(capabilities) = capabilities {
5156 request.capabilities = capabilities;
5157 }
5158 request.commands.push(command);
5159 saw_command = true;
5160 }
5161 PktLineFrame::Data(_) => {
5162 return Err(GitError::InvalidFormat(
5163 "receive-pack request has data after flush".into(),
5164 ));
5165 }
5166 PktLineFrame::Flush => {
5167 saw_flush = true;
5168 if idx + 1 != frames.len() {
5169 return Err(GitError::InvalidFormat(
5170 "receive-pack request has frames after flush".into(),
5171 ));
5172 }
5173 }
5174 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5175 return Err(GitError::InvalidFormat(
5176 "receive-pack request contains a non-flush control packet".into(),
5177 ));
5178 }
5179 }
5180 }
5181 if !saw_flush {
5182 return Err(GitError::InvalidFormat(
5183 "receive-pack request missing flush".into(),
5184 ));
5185 }
5186 if !request.shallow.is_empty() && request.commands.is_empty() {
5187 return Err(GitError::InvalidFormat(
5188 "receive-pack request has shallow lines without commands".into(),
5189 ));
5190 }
5191 Ok(request)
5192}
5193
5194pub fn encode_receive_pack_request(request: &ReceivePackRequest) -> Result<Vec<PktLineFrame>> {
5195 if !request.shallow.is_empty() && request.commands.is_empty() {
5196 return Err(GitError::InvalidFormat(
5197 "receive-pack request has shallow lines without commands".into(),
5198 ));
5199 }
5200
5201 let mut frames = Vec::new();
5202 for oid in &request.shallow {
5203 frames.push(PktLineFrame::data(line_from_str(&format!(
5204 "shallow {oid}"
5205 )))?);
5206 }
5207 for (idx, command) in request.commands.iter().enumerate() {
5208 let mut payload = format_receive_pack_command(command)?;
5209 if idx == 0 && !request.capabilities.is_empty() {
5210 payload.push(0);
5211 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5212 }
5213 payload.push(b'\n');
5214 frames.push(PktLineFrame::data(payload)?);
5215 }
5216 frames.push(PktLineFrame::Flush);
5217 Ok(frames)
5218}
5219
5220pub fn read_receive_pack_request(
5221 format: ObjectFormat,
5222 reader: &mut impl Read,
5223) -> Result<ReceivePackRequest> {
5224 let frames = read_pkt_line_frames_until_flush(reader)?;
5225 parse_receive_pack_request(format, &frames)
5226}
5227
5228pub fn write_receive_pack_request(
5229 writer: &mut impl Write,
5230 request: &ReceivePackRequest,
5231) -> Result<()> {
5232 if !request.shallow.is_empty() && request.commands.is_empty() {
5233 return Err(GitError::InvalidFormat(
5234 "receive-pack request has shallow lines without commands".into(),
5235 ));
5236 }
5237
5238 for oid in &request.shallow {
5239 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
5240 }
5241 for (idx, command) in request.commands.iter().enumerate() {
5242 let mut payload = format_receive_pack_command(command)?;
5243 if idx == 0 && !request.capabilities.is_empty() {
5244 payload.push(0);
5245 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5246 }
5247 payload.push(b'\n');
5248 write_pkt_line_payload(writer, &payload)?;
5249 }
5250 writer.write_all(b"0000")?;
5251 Ok(())
5252}
5253
5254pub fn parse_receive_pack_push_request(
5255 format: ObjectFormat,
5256 input: &[u8],
5257 has_push_options: bool,
5258) -> Result<ReceivePackPushRequest> {
5259 let (command_frames, consumed) = parse_pkt_line_frames_until_flush_from(input)?;
5260 let commands = parse_receive_pack_request(format, &command_frames)?;
5261 let mut offset = consumed;
5262 let push_options = if has_push_options {
5263 let (push_option_frames, consumed) =
5264 parse_pkt_line_frames_until_flush_from(&input[offset..])?;
5265 offset += consumed;
5266 Some(parse_receive_pack_push_options(&push_option_frames)?)
5267 } else {
5268 None
5269 };
5270 Ok(ReceivePackPushRequest {
5271 commands,
5272 push_options,
5273 packfile: input[offset..].to_vec(),
5274 })
5275}
5276
5277pub fn encode_receive_pack_push_request(request: &ReceivePackPushRequest) -> Result<Vec<u8>> {
5278 let mut out = Vec::new();
5279 write_receive_pack_request(&mut out, &request.commands)?;
5280 if let Some(push_options) = &request.push_options {
5281 write_receive_pack_push_options(&mut out, push_options)?;
5282 }
5283 out.extend_from_slice(&request.packfile);
5284 Ok(out)
5285}
5286
5287pub fn read_receive_pack_push_request(
5288 format: ObjectFormat,
5289 reader: &mut impl Read,
5290 has_push_options: bool,
5291) -> Result<ReceivePackPushRequest> {
5292 let commands = read_receive_pack_request(format, reader)?;
5293 let push_options = if has_push_options {
5294 Some(read_receive_pack_push_options(reader)?)
5295 } else {
5296 None
5297 };
5298 let mut packfile = Vec::new();
5299 reader.read_to_end(&mut packfile)?;
5300 Ok(ReceivePackPushRequest {
5301 commands,
5302 push_options,
5303 packfile,
5304 })
5305}
5306
5307pub fn write_receive_pack_push_request(
5308 writer: &mut impl Write,
5309 request: &ReceivePackPushRequest,
5310) -> Result<()> {
5311 write_receive_pack_request(writer, &request.commands)?;
5312 if let Some(push_options) = &request.push_options {
5313 write_receive_pack_push_options(writer, push_options)?;
5314 }
5315 writer.write_all(&request.packfile)?;
5316 Ok(())
5317}
5318
5319pub fn parse_receive_pack_features(capabilities: &[Capability]) -> Result<ReceivePackFeatures> {
5320 let mut features = ReceivePackFeatures::default();
5321 for capability in capabilities {
5322 match capability.name.as_str() {
5323 "report-status" => {
5324 reject_capability_value("receive-pack report-status", capability)?;
5325 if features.report_status {
5326 return Err(GitError::InvalidFormat(
5327 "receive-pack has duplicate report-status capability".into(),
5328 ));
5329 }
5330 features.report_status = true;
5331 }
5332 "report-status-v2" => {
5333 reject_capability_value("receive-pack report-status-v2", capability)?;
5334 if features.report_status_v2 {
5335 return Err(GitError::InvalidFormat(
5336 "receive-pack has duplicate report-status-v2 capability".into(),
5337 ));
5338 }
5339 features.report_status_v2 = true;
5340 }
5341 "delete-refs" => {
5342 reject_capability_value("receive-pack delete-refs", capability)?;
5343 if features.delete_refs {
5344 return Err(GitError::InvalidFormat(
5345 "receive-pack has duplicate delete-refs capability".into(),
5346 ));
5347 }
5348 features.delete_refs = true;
5349 }
5350 "ofs-delta" => {
5351 reject_capability_value("receive-pack ofs-delta", capability)?;
5352 if features.ofs_delta {
5353 return Err(GitError::InvalidFormat(
5354 "receive-pack has duplicate ofs-delta capability".into(),
5355 ));
5356 }
5357 features.ofs_delta = true;
5358 }
5359 "atomic" => {
5360 reject_capability_value("receive-pack atomic", capability)?;
5361 if features.atomic {
5362 return Err(GitError::InvalidFormat(
5363 "receive-pack has duplicate atomic capability".into(),
5364 ));
5365 }
5366 features.atomic = true;
5367 }
5368 "push-options" => {
5369 reject_capability_value("receive-pack push-options", capability)?;
5370 if features.push_options {
5371 return Err(GitError::InvalidFormat(
5372 "receive-pack has duplicate push-options capability".into(),
5373 ));
5374 }
5375 features.push_options = true;
5376 }
5377 "side-band-64k" => {
5378 reject_capability_value("receive-pack side-band-64k", capability)?;
5379 if features.side_band_64k {
5380 return Err(GitError::InvalidFormat(
5381 "receive-pack has duplicate side-band-64k capability".into(),
5382 ));
5383 }
5384 features.side_band_64k = true;
5385 }
5386 "quiet" => {
5387 reject_capability_value("receive-pack quiet", capability)?;
5388 if features.quiet {
5389 return Err(GitError::InvalidFormat(
5390 "receive-pack has duplicate quiet capability".into(),
5391 ));
5392 }
5393 features.quiet = true;
5394 }
5395 "no-thin" => {
5396 reject_capability_value("receive-pack no-thin", capability)?;
5397 if features.no_thin {
5398 return Err(GitError::InvalidFormat(
5399 "receive-pack has duplicate no-thin capability".into(),
5400 ));
5401 }
5402 features.no_thin = true;
5403 }
5404 "agent" => {
5405 let Some(agent) = &capability.value else {
5406 return Err(GitError::InvalidFormat(
5407 "receive-pack agent capability is missing value".into(),
5408 ));
5409 };
5410 if features.agent.is_some() {
5411 return Err(GitError::InvalidFormat(
5412 "receive-pack has duplicate agent capability".into(),
5413 ));
5414 }
5415 validate_capability_field("receive-pack agent", agent)?;
5416 features.agent = Some(agent.clone());
5417 }
5418 "object-format" => {
5419 let Some(format) = &capability.value else {
5420 return Err(GitError::InvalidFormat(
5421 "receive-pack object-format capability is missing value".into(),
5422 ));
5423 };
5424 if features.object_format.is_some() {
5425 return Err(GitError::InvalidFormat(
5426 "receive-pack has duplicate object-format capability".into(),
5427 ));
5428 }
5429 validate_capability_field("receive-pack object-format", format)?;
5430 features.object_format = Some(format.parse()?);
5431 }
5432 _ => {
5433 encode_capabilities(std::slice::from_ref(capability))?;
5434 if features
5435 .unknown
5436 .iter()
5437 .any(|known| known.name == capability.name)
5438 {
5439 return Err(GitError::InvalidFormat(format!(
5440 "receive-pack has duplicate {} capability",
5441 capability.name
5442 )));
5443 }
5444 features.unknown.push(capability.clone());
5445 }
5446 }
5447 }
5448 Ok(features)
5449}
5450
5451pub fn encode_receive_pack_features(features: &ReceivePackFeatures) -> Result<Vec<Capability>> {
5452 let mut capabilities = Vec::new();
5453 if features.report_status {
5454 capabilities.push(Capability {
5455 name: "report-status".into(),
5456 value: None,
5457 });
5458 }
5459 if features.report_status_v2 {
5460 capabilities.push(Capability {
5461 name: "report-status-v2".into(),
5462 value: None,
5463 });
5464 }
5465 if features.delete_refs {
5466 capabilities.push(Capability {
5467 name: "delete-refs".into(),
5468 value: None,
5469 });
5470 }
5471 if features.ofs_delta {
5472 capabilities.push(Capability {
5473 name: "ofs-delta".into(),
5474 value: None,
5475 });
5476 }
5477 if features.atomic {
5478 capabilities.push(Capability {
5479 name: "atomic".into(),
5480 value: None,
5481 });
5482 }
5483 if features.push_options {
5484 capabilities.push(Capability {
5485 name: "push-options".into(),
5486 value: None,
5487 });
5488 }
5489 if features.side_band_64k {
5490 capabilities.push(Capability {
5491 name: "side-band-64k".into(),
5492 value: None,
5493 });
5494 }
5495 if features.quiet {
5496 capabilities.push(Capability {
5497 name: "quiet".into(),
5498 value: None,
5499 });
5500 }
5501 if features.no_thin {
5502 capabilities.push(Capability {
5503 name: "no-thin".into(),
5504 value: None,
5505 });
5506 }
5507 if let Some(agent) = &features.agent {
5508 validate_capability_field("receive-pack agent", agent)?;
5509 capabilities.push(Capability {
5510 name: "agent".into(),
5511 value: Some(agent.clone()),
5512 });
5513 }
5514 if let Some(format) = features.object_format {
5515 capabilities.push(Capability {
5516 name: "object-format".into(),
5517 value: Some(format.name().into()),
5518 });
5519 }
5520 for capability in &features.unknown {
5521 if is_known_receive_pack_capability(&capability.name) {
5522 return Err(GitError::InvalidFormat(format!(
5523 "receive-pack unknown capability duplicates known capability {}",
5524 capability.name
5525 )));
5526 }
5527 encode_capabilities(std::slice::from_ref(capability))?;
5528 capabilities.push(capability.clone());
5529 }
5530 Ok(capabilities)
5531}
5532
5533pub fn validate_receive_pack_push_request_features(
5534 features: &ReceivePackFeatures,
5535 request: &ReceivePackPushRequest,
5536) -> Result<()> {
5537 for capability in &request.commands.capabilities {
5538 if matches!(
5539 capability.name.as_str(),
5540 "report-status"
5541 | "report-status-v2"
5542 | "delete-refs"
5543 | "ofs-delta"
5544 | "atomic"
5545 | "push-options"
5546 | "side-band-64k"
5547 | "quiet"
5548 | "no-thin"
5549 ) {
5550 reject_capability_value("receive-pack request capability", capability)?;
5551 }
5552 match capability.name.as_str() {
5553 "report-status" if !features.report_status => {
5554 return Err(GitError::InvalidFormat(
5555 "receive-pack request uses report-status without advertised capability".into(),
5556 ));
5557 }
5558 "report-status-v2" if !features.report_status_v2 => {
5559 return Err(GitError::InvalidFormat(
5560 "receive-pack request uses report-status-v2 without advertised capability"
5561 .into(),
5562 ));
5563 }
5564 "delete-refs" if !features.delete_refs => {
5565 return Err(GitError::InvalidFormat(
5566 "receive-pack request uses delete-refs without advertised capability".into(),
5567 ));
5568 }
5569 "ofs-delta" if !features.ofs_delta => {
5570 return Err(GitError::InvalidFormat(
5571 "receive-pack request uses ofs-delta without advertised capability".into(),
5572 ));
5573 }
5574 "atomic" if !features.atomic => {
5575 return Err(GitError::InvalidFormat(
5576 "receive-pack request uses atomic without advertised capability".into(),
5577 ));
5578 }
5579 "push-options" if !features.push_options => {
5580 return Err(GitError::InvalidFormat(
5581 "receive-pack request uses push-options without advertised capability".into(),
5582 ));
5583 }
5584 "side-band-64k" if !features.side_band_64k => {
5585 return Err(GitError::InvalidFormat(
5586 "receive-pack request uses side-band-64k without advertised capability".into(),
5587 ));
5588 }
5589 "quiet" if !features.quiet => {
5590 return Err(GitError::InvalidFormat(
5591 "receive-pack request uses quiet without advertised capability".into(),
5592 ));
5593 }
5594 "no-thin" => {
5595 return Err(GitError::InvalidFormat(
5596 "receive-pack request must not request no-thin".into(),
5597 ));
5598 }
5599 "agent" => {
5600 validate_capability_field(
5601 "receive-pack request agent",
5602 capability.value.as_deref().unwrap_or_default(),
5603 )?;
5604 }
5605 "object-format" => {
5606 let Some(value) = &capability.value else {
5607 return Err(GitError::InvalidFormat(
5608 "receive-pack request object-format capability is missing value".into(),
5609 ));
5610 };
5611 let requested_format: ObjectFormat = value.parse()?;
5612 if features.object_format != Some(requested_format) {
5613 return Err(GitError::InvalidFormat(
5614 "receive-pack request object-format was not advertised".into(),
5615 ));
5616 }
5617 }
5618 name if is_known_receive_pack_capability(name) => {}
5619 _ => {
5620 if !features
5621 .unknown
5622 .iter()
5623 .any(|advertised| advertised.name == capability.name)
5624 {
5625 return Err(GitError::InvalidFormat(format!(
5626 "receive-pack request uses unadvertised capability {}",
5627 capability.name
5628 )));
5629 }
5630 }
5631 }
5632 }
5633
5634 let requested_push_options = request
5635 .commands
5636 .capabilities
5637 .iter()
5638 .any(|capability| capability.name == "push-options");
5639 match (requested_push_options, &request.push_options) {
5640 (true, Some(_)) => {}
5641 (true, None) => {
5642 return Err(GitError::InvalidFormat(
5643 "receive-pack request uses push-options without push-options section".into(),
5644 ));
5645 }
5646 (false, Some(_)) => {
5647 return Err(GitError::InvalidFormat(
5648 "receive-pack request has push-options section without requested capability".into(),
5649 ));
5650 }
5651 (false, None) => {}
5652 }
5653
5654 let has_delete = request
5655 .commands
5656 .commands
5657 .iter()
5658 .any(is_receive_pack_delete_command);
5659 if has_delete && !features.delete_refs {
5660 return Err(GitError::InvalidFormat(
5661 "receive-pack request deletes refs without advertised delete-refs capability".into(),
5662 ));
5663 }
5664
5665 let has_update_or_create = request
5666 .commands
5667 .commands
5668 .iter()
5669 .any(|command| !is_receive_pack_delete_command(command));
5670 if has_update_or_create && request.packfile.is_empty() {
5671 return Err(GitError::InvalidFormat(
5672 "receive-pack request with create/update commands is missing packfile".into(),
5673 ));
5674 }
5675 if !has_update_or_create && !request.packfile.is_empty() {
5676 return Err(GitError::InvalidFormat(
5677 "receive-pack delete-only request must not include packfile".into(),
5678 ));
5679 }
5680 Ok(())
5681}
5682
5683pub fn apply_receive_pack_push_request<R, I, C, U, D>(
5684 features: &ReceivePackFeatures,
5685 request: &ReceivePackPushRequest,
5686 mut read_ref: R,
5687 mut install_pack: I,
5688 mut contains_object: C,
5689 mut apply_updates: U,
5690 mut delete_ref: D,
5691) -> Result<ReceivePackReportStatus>
5692where
5693 R: FnMut(&str) -> Result<Option<ObjectId>>,
5694 I: FnMut(&[u8]) -> Result<()>,
5695 C: FnMut(&ObjectId) -> Result<bool>,
5696 U: FnMut(&[ReceivePackCommand]) -> Result<()>,
5697 D: FnMut(&ReceivePackCommand) -> Result<()>,
5698{
5699 validate_receive_pack_push_request_features(features, request)?;
5700
5701 for command in request
5702 .commands
5703 .commands
5704 .iter()
5705 .filter(|command| is_receive_pack_delete_command(command))
5706 {
5707 if !command.old_id.is_null() && read_ref(&command.name)? != Some(command.old_id.clone()) {
5708 return Err(GitError::Transaction(format!(
5709 "expected ref {} to match",
5710 command.name
5711 )));
5712 }
5713 }
5714
5715 let updates = request
5716 .commands
5717 .commands
5718 .iter()
5719 .filter(|command| !is_receive_pack_delete_command(command))
5720 .cloned()
5721 .collect::<Vec<_>>();
5722 if !updates.is_empty() {
5723 install_pack(&request.packfile)?;
5724 for command in &updates {
5725 if !contains_object(&command.new_id)? {
5726 return Err(GitError::InvalidObject(format!(
5727 "receive-pack packfile did not provide {}",
5728 command.new_id
5729 )));
5730 }
5731 }
5732 apply_updates(&updates)?;
5733 }
5734
5735 for command in request
5736 .commands
5737 .commands
5738 .iter()
5739 .filter(|command| is_receive_pack_delete_command(command))
5740 {
5741 delete_ref(command)?;
5742 }
5743
5744 Ok(ReceivePackReportStatus {
5745 unpack: ReceivePackUnpackStatus::Ok,
5746 commands: request
5747 .commands
5748 .commands
5749 .iter()
5750 .map(|command| ReceivePackCommandStatus::Ok {
5751 name: command.name.clone(),
5752 })
5753 .collect(),
5754 })
5755}
5756
5757pub fn parse_receive_pack_report_status(
5758 frames: &[PktLineFrame],
5759) -> Result<ReceivePackReportStatus> {
5760 let Some((first, rest)) = frames.split_first() else {
5761 return Err(GitError::InvalidFormat(
5762 "receive-pack report-status is empty".into(),
5763 ));
5764 };
5765 let PktLineFrame::Data(payload) = first else {
5766 return Err(GitError::InvalidFormat(
5767 "receive-pack report-status must start with unpack status".into(),
5768 ));
5769 };
5770 let unpack = parse_receive_pack_unpack_status(payload)?;
5771
5772 let mut commands = Vec::new();
5773 let mut saw_flush = false;
5774 for (idx, frame) in rest.iter().enumerate() {
5775 match frame {
5776 PktLineFrame::Data(payload) if !saw_flush => {
5777 commands.push(parse_receive_pack_command_status(payload)?);
5778 }
5779 PktLineFrame::Data(_) => {
5780 return Err(GitError::InvalidFormat(
5781 "receive-pack report-status has data after flush".into(),
5782 ));
5783 }
5784 PktLineFrame::Flush => {
5785 saw_flush = true;
5786 if idx + 1 != rest.len() {
5787 return Err(GitError::InvalidFormat(
5788 "receive-pack report-status has frames after flush".into(),
5789 ));
5790 }
5791 }
5792 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5793 return Err(GitError::InvalidFormat(
5794 "receive-pack report-status contains a non-flush control packet".into(),
5795 ));
5796 }
5797 }
5798 }
5799 if !saw_flush {
5800 return Err(GitError::InvalidFormat(
5801 "receive-pack report-status missing flush".into(),
5802 ));
5803 }
5804 Ok(ReceivePackReportStatus { unpack, commands })
5805}
5806
5807pub fn encode_receive_pack_report_status(
5808 report: &ReceivePackReportStatus,
5809) -> Result<Vec<PktLineFrame>> {
5810 let mut frames = Vec::new();
5811 frames.push(PktLineFrame::data(line_from_str(
5812 &format_receive_pack_unpack_status(&report.unpack)?,
5813 ))?);
5814 for command in &report.commands {
5815 frames.push(PktLineFrame::data(line_from_str(
5816 &format_receive_pack_command_status(command)?,
5817 ))?);
5818 }
5819 frames.push(PktLineFrame::Flush);
5820 Ok(frames)
5821}
5822
5823pub fn read_receive_pack_report_status(reader: &mut impl Read) -> Result<ReceivePackReportStatus> {
5824 let frames = read_pkt_line_frames_until_flush(reader)?;
5825 parse_receive_pack_report_status(&frames)
5826}
5827
5828pub fn write_receive_pack_report_status(
5829 writer: &mut impl Write,
5830 report: &ReceivePackReportStatus,
5831) -> Result<()> {
5832 write_pkt_line_payload(
5833 writer,
5834 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5835 )?;
5836 for command in &report.commands {
5837 write_pkt_line_payload(
5838 writer,
5839 &line_from_str(&format_receive_pack_command_status(command)?),
5840 )?;
5841 }
5842 writer.write_all(b"0000")?;
5843 Ok(())
5844}
5845
5846pub fn parse_receive_pack_report_status_v2(
5847 format: ObjectFormat,
5848 frames: &[PktLineFrame],
5849) -> Result<ReceivePackReportStatusV2> {
5850 let Some((first, rest)) = frames.split_first() else {
5851 return Err(GitError::InvalidFormat(
5852 "receive-pack report-status-v2 is empty".into(),
5853 ));
5854 };
5855 let PktLineFrame::Data(payload) = first else {
5856 return Err(GitError::InvalidFormat(
5857 "receive-pack report-status-v2 must start with unpack status".into(),
5858 ));
5859 };
5860 let unpack = parse_receive_pack_unpack_status(payload)?;
5861
5862 let mut commands = Vec::new();
5863 let mut saw_flush = false;
5864 for (idx, frame) in rest.iter().enumerate() {
5865 match frame {
5866 PktLineFrame::Data(payload) if !saw_flush => {
5867 let text =
5868 parse_protocol_v2_line_text("receive-pack report-status-v2 line", payload)?;
5869 if text.starts_with("option ") {
5870 let Some(ReceivePackCommandStatusV2::Ok { options, .. }) = commands.last_mut()
5871 else {
5872 return Err(GitError::InvalidFormat(
5873 "receive-pack report-status-v2 option without ok status".into(),
5874 ));
5875 };
5876 parse_receive_pack_report_status_v2_option(format, text, options)?;
5877 } else {
5878 commands.push(parse_receive_pack_command_status_v2(text)?);
5879 }
5880 }
5881 PktLineFrame::Data(_) => {
5882 return Err(GitError::InvalidFormat(
5883 "receive-pack report-status-v2 has data after flush".into(),
5884 ));
5885 }
5886 PktLineFrame::Flush => {
5887 saw_flush = true;
5888 if idx + 1 != rest.len() {
5889 return Err(GitError::InvalidFormat(
5890 "receive-pack report-status-v2 has frames after flush".into(),
5891 ));
5892 }
5893 }
5894 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5895 return Err(GitError::InvalidFormat(
5896 "receive-pack report-status-v2 contains a non-flush control packet".into(),
5897 ));
5898 }
5899 }
5900 }
5901 if !saw_flush {
5902 return Err(GitError::InvalidFormat(
5903 "receive-pack report-status-v2 missing flush".into(),
5904 ));
5905 }
5906 Ok(ReceivePackReportStatusV2 { unpack, commands })
5907}
5908
5909pub fn encode_receive_pack_report_status_v2(
5910 report: &ReceivePackReportStatusV2,
5911) -> Result<Vec<PktLineFrame>> {
5912 let mut frames = Vec::new();
5913 frames.push(PktLineFrame::data(line_from_str(
5914 &format_receive_pack_unpack_status(&report.unpack)?,
5915 ))?);
5916 for command in &report.commands {
5917 frames.push(PktLineFrame::data(line_from_str(
5918 &format_receive_pack_command_status_v2(command)?,
5919 ))?);
5920 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5921 for option in format_receive_pack_report_status_v2_options(options)? {
5922 frames.push(PktLineFrame::data(line_from_str(&option))?);
5923 }
5924 }
5925 }
5926 frames.push(PktLineFrame::Flush);
5927 Ok(frames)
5928}
5929
5930pub fn read_receive_pack_report_status_v2(
5931 format: ObjectFormat,
5932 reader: &mut impl Read,
5933) -> Result<ReceivePackReportStatusV2> {
5934 let frames = read_pkt_line_frames_until_flush(reader)?;
5935 parse_receive_pack_report_status_v2(format, &frames)
5936}
5937
5938pub fn write_receive_pack_report_status_v2(
5939 writer: &mut impl Write,
5940 report: &ReceivePackReportStatusV2,
5941) -> Result<()> {
5942 write_pkt_line_payload(
5943 writer,
5944 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5945 )?;
5946 for command in &report.commands {
5947 write_pkt_line_payload(
5948 writer,
5949 &line_from_str(&format_receive_pack_command_status_v2(command)?),
5950 )?;
5951 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5952 for option in format_receive_pack_report_status_v2_options(options)? {
5953 write_pkt_line_payload(writer, &line_from_str(&option))?;
5954 }
5955 }
5956 }
5957 writer.write_all(b"0000")?;
5958 Ok(())
5959}
5960
5961pub fn parse_receive_pack_push_options(frames: &[PktLineFrame]) -> Result<Vec<String>> {
5962 let mut options = Vec::new();
5963 let mut saw_flush = false;
5964 for (idx, frame) in frames.iter().enumerate() {
5965 match frame {
5966 PktLineFrame::Data(payload) if !saw_flush => {
5967 let option = trim_trailing_lf(payload);
5968 validate_receive_pack_push_option(option)?;
5969 options.push(
5970 std::str::from_utf8(option)
5971 .map_err(|err| GitError::InvalidFormat(err.to_string()))?
5972 .to_string(),
5973 );
5974 }
5975 PktLineFrame::Data(_) => {
5976 return Err(GitError::InvalidFormat(
5977 "receive-pack push-options has data after flush".into(),
5978 ));
5979 }
5980 PktLineFrame::Flush => {
5981 saw_flush = true;
5982 if idx + 1 != frames.len() {
5983 return Err(GitError::InvalidFormat(
5984 "receive-pack push-options has frames after flush".into(),
5985 ));
5986 }
5987 }
5988 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5989 return Err(GitError::InvalidFormat(
5990 "receive-pack push-options contains a non-flush control packet".into(),
5991 ));
5992 }
5993 }
5994 }
5995 if !saw_flush {
5996 return Err(GitError::InvalidFormat(
5997 "receive-pack push-options missing flush".into(),
5998 ));
5999 }
6000 Ok(options)
6001}
6002
6003pub fn encode_receive_pack_push_options(options: &[String]) -> Result<Vec<PktLineFrame>> {
6004 let mut frames = Vec::new();
6005 for option in options {
6006 validate_receive_pack_push_option(option.as_bytes())?;
6007 let mut payload = option.as_bytes().to_vec();
6008 payload.push(b'\n');
6009 frames.push(PktLineFrame::data(payload)?);
6010 }
6011 frames.push(PktLineFrame::Flush);
6012 Ok(frames)
6013}
6014
6015pub fn read_receive_pack_push_options(reader: &mut impl Read) -> Result<Vec<String>> {
6016 let frames = read_pkt_line_frames_until_flush(reader)?;
6017 parse_receive_pack_push_options(&frames)
6018}
6019
6020pub fn write_receive_pack_push_options(writer: &mut impl Write, options: &[String]) -> Result<()> {
6021 for option in options {
6022 validate_receive_pack_push_option(option.as_bytes())?;
6023 let mut payload = option.as_bytes().to_vec();
6024 payload.push(b'\n');
6025 write_pkt_line_payload(writer, &payload)?;
6026 }
6027 writer.write_all(b"0000")?;
6028 Ok(())
6029}
6030
6031fn parse_receive_pack_command(format: ObjectFormat, payload: &[u8]) -> Result<ReceivePackCommand> {
6032 let text =
6033 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6034 let mut fields = text.split(' ');
6035 let old_id = fields
6036 .next()
6037 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing old id".into()))?;
6038 let new_id = fields
6039 .next()
6040 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing new id".into()))?;
6041 let name = fields
6042 .next()
6043 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing ref name".into()))?;
6044 if fields.next().is_some() {
6045 return Err(GitError::InvalidFormat(
6046 "receive-pack command has too many fields".into(),
6047 ));
6048 }
6049 validate_protocol_v2_token("receive-pack old id", old_id)?;
6050 validate_protocol_v2_token("receive-pack new id", new_id)?;
6051 validate_protocol_v2_token("receive-pack ref name", name)?;
6052 Ok(ReceivePackCommand {
6053 old_id: ObjectId::from_hex(format, old_id)?,
6054 new_id: ObjectId::from_hex(format, new_id)?,
6055 name: name.to_string(),
6056 })
6057}
6058
6059fn format_receive_pack_command(command: &ReceivePackCommand) -> Result<Vec<u8>> {
6060 if command.old_id.format() != command.new_id.format() {
6061 return Err(GitError::InvalidObjectId(
6062 "receive-pack command object formats do not match".into(),
6063 ));
6064 }
6065 validate_protocol_v2_token("receive-pack ref name", &command.name)?;
6066 Ok(format!("{} {} {}", command.old_id, command.new_id, command.name).into_bytes())
6067}
6068
6069fn set_upload_pack_flag(value: &mut bool, capability: &Capability) -> Result<()> {
6070 reject_capability_value("upload-pack capability", capability)?;
6071 if *value {
6072 return Err(GitError::InvalidFormat(format!(
6073 "upload-pack has duplicate {} capability",
6074 capability.name
6075 )));
6076 }
6077 *value = true;
6078 Ok(())
6079}
6080
6081fn push_upload_pack_flag(capabilities: &mut Vec<Capability>, name: &str, enabled: bool) {
6082 if enabled {
6083 capabilities.push(Capability {
6084 name: name.into(),
6085 value: None,
6086 });
6087 }
6088}
6089
6090fn is_known_upload_pack_capability(name: &str) -> bool {
6091 matches!(
6092 name,
6093 "multi_ack"
6094 | "multi_ack_detailed"
6095 | "no-done"
6096 | "thin-pack"
6097 | "side-band"
6098 | "side-band-64k"
6099 | "ofs-delta"
6100 | "shallow"
6101 | "deepen-since"
6102 | "deepen-not"
6103 | "include-tag"
6104 | "no-progress"
6105 | "allow-tip-sha1-in-want"
6106 | "allow-reachable-sha1-in-want"
6107 | "filter"
6108 | "agent"
6109 | "object-format"
6110 | "symref"
6111 )
6112}
6113
6114fn is_upload_pack_flag_capability(name: &str) -> bool {
6115 matches!(
6116 name,
6117 "multi_ack"
6118 | "multi_ack_detailed"
6119 | "no-done"
6120 | "thin-pack"
6121 | "side-band"
6122 | "side-band-64k"
6123 | "ofs-delta"
6124 | "shallow"
6125 | "deepen-since"
6126 | "deepen-not"
6127 | "include-tag"
6128 | "no-progress"
6129 | "allow-tip-sha1-in-want"
6130 | "allow-reachable-sha1-in-want"
6131 | "filter"
6132 )
6133}
6134
6135fn reject_capability_value(label: &str, capability: &Capability) -> Result<()> {
6136 if capability.value.is_some() {
6137 return Err(GitError::InvalidFormat(format!(
6138 "{label} must not have value"
6139 )));
6140 }
6141 Ok(())
6142}
6143
6144fn is_known_receive_pack_capability(name: &str) -> bool {
6145 matches!(
6146 name,
6147 "report-status"
6148 | "report-status-v2"
6149 | "delete-refs"
6150 | "ofs-delta"
6151 | "atomic"
6152 | "push-options"
6153 | "side-band-64k"
6154 | "quiet"
6155 | "no-thin"
6156 | "agent"
6157 | "object-format"
6158 )
6159}
6160
6161fn is_receive_pack_delete_command(command: &ReceivePackCommand) -> bool {
6162 command.new_id.is_null()
6163}
6164
6165fn parse_receive_pack_unpack_status(payload: &[u8]) -> Result<ReceivePackUnpackStatus> {
6166 let text = parse_protocol_v2_line_text("receive-pack unpack status", payload)?;
6167 if text == "unpack ok" {
6168 return Ok(ReceivePackUnpackStatus::Ok);
6169 }
6170 let Some(message) = text.strip_prefix("unpack ") else {
6171 return Err(GitError::InvalidFormat(format!(
6172 "unsupported receive-pack unpack status {text}"
6173 )));
6174 };
6175 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6176 Ok(ReceivePackUnpackStatus::Error(message.to_string()))
6177}
6178
6179fn format_receive_pack_unpack_status(status: &ReceivePackUnpackStatus) -> Result<String> {
6180 match status {
6181 ReceivePackUnpackStatus::Ok => Ok("unpack ok".into()),
6182 ReceivePackUnpackStatus::Error(message) => {
6183 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6184 Ok(format!("unpack {message}"))
6185 }
6186 }
6187}
6188
6189fn parse_receive_pack_command_status(payload: &[u8]) -> Result<ReceivePackCommandStatus> {
6190 let text = parse_protocol_v2_line_text("receive-pack command status", payload)?;
6191 if let Some(name) = text.strip_prefix("ok ") {
6192 validate_protocol_v2_token("receive-pack status ref name", name)?;
6193 return Ok(ReceivePackCommandStatus::Ok {
6194 name: name.to_string(),
6195 });
6196 }
6197 let Some(rest) = text.strip_prefix("ng ") else {
6198 return Err(GitError::InvalidFormat(format!(
6199 "unsupported receive-pack command status {text}"
6200 )));
6201 };
6202 let (name, message) = rest
6203 .split_once(' ')
6204 .ok_or_else(|| GitError::InvalidFormat("receive-pack ng status missing message".into()))?;
6205 validate_protocol_v2_token("receive-pack status ref name", name)?;
6206 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6207 Ok(ReceivePackCommandStatus::Ng {
6208 name: name.to_string(),
6209 message: message.to_string(),
6210 })
6211}
6212
6213fn format_receive_pack_command_status(status: &ReceivePackCommandStatus) -> Result<String> {
6214 match status {
6215 ReceivePackCommandStatus::Ok { name } => {
6216 validate_protocol_v2_token("receive-pack status ref name", name)?;
6217 Ok(format!("ok {name}"))
6218 }
6219 ReceivePackCommandStatus::Ng { name, message } => {
6220 validate_protocol_v2_token("receive-pack status ref name", name)?;
6221 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6222 Ok(format!("ng {name} {message}"))
6223 }
6224 }
6225}
6226
6227fn parse_receive_pack_command_status_v2(text: &str) -> Result<ReceivePackCommandStatusV2> {
6228 if let Some(name) = text.strip_prefix("ok ") {
6229 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6230 return Ok(ReceivePackCommandStatusV2::Ok {
6231 name: name.to_string(),
6232 options: ReceivePackCommandStatusV2Options::default(),
6233 });
6234 }
6235 let Some(rest) = text.strip_prefix("ng ") else {
6236 return Err(GitError::InvalidFormat(format!(
6237 "unsupported receive-pack report-status-v2 line {text}"
6238 )));
6239 };
6240 let (name, message) = rest.split_once(' ').ok_or_else(|| {
6241 GitError::InvalidFormat("receive-pack status-v2 ng status missing message".into())
6242 })?;
6243 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6244 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6245 Ok(ReceivePackCommandStatusV2::Ng {
6246 name: name.to_string(),
6247 message: message.to_string(),
6248 })
6249}
6250
6251fn format_receive_pack_command_status_v2(status: &ReceivePackCommandStatusV2) -> Result<String> {
6252 match status {
6253 ReceivePackCommandStatusV2::Ok { name, .. } => {
6254 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6255 Ok(format!("ok {name}"))
6256 }
6257 ReceivePackCommandStatusV2::Ng { name, message } => {
6258 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6259 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6260 Ok(format!("ng {name} {message}"))
6261 }
6262 }
6263}
6264
6265fn parse_receive_pack_report_status_v2_option(
6266 format: ObjectFormat,
6267 text: &str,
6268 options: &mut ReceivePackCommandStatusV2Options,
6269) -> Result<()> {
6270 if let Some(refname) = text.strip_prefix("option refname ") {
6271 if options.refname.is_some() {
6272 return Err(GitError::InvalidFormat(
6273 "receive-pack report-status-v2 has duplicate refname option".into(),
6274 ));
6275 }
6276 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6277 options.refname = Some(refname.to_string());
6278 } else if let Some(old_oid) = text.strip_prefix("option old-oid ") {
6279 if options.old_oid.is_some() {
6280 return Err(GitError::InvalidFormat(
6281 "receive-pack report-status-v2 has duplicate old-oid option".into(),
6282 ));
6283 }
6284 validate_protocol_v2_token("receive-pack status-v2 option old-oid", old_oid)?;
6285 options.old_oid = Some(ObjectId::from_hex(format, old_oid)?);
6286 } else if let Some(new_oid) = text.strip_prefix("option new-oid ") {
6287 if options.new_oid.is_some() {
6288 return Err(GitError::InvalidFormat(
6289 "receive-pack report-status-v2 has duplicate new-oid option".into(),
6290 ));
6291 }
6292 validate_protocol_v2_token("receive-pack status-v2 option new-oid", new_oid)?;
6293 options.new_oid = Some(ObjectId::from_hex(format, new_oid)?);
6294 } else if text == "option forced-update" {
6295 if options.forced_update {
6296 return Err(GitError::InvalidFormat(
6297 "receive-pack report-status-v2 has duplicate forced-update option".into(),
6298 ));
6299 }
6300 options.forced_update = true;
6301 } else {
6302 return Err(GitError::InvalidFormat(format!(
6303 "unsupported receive-pack report-status-v2 option {text}"
6304 )));
6305 }
6306 Ok(())
6307}
6308
6309fn format_receive_pack_report_status_v2_options(
6310 options: &ReceivePackCommandStatusV2Options,
6311) -> Result<Vec<String>> {
6312 let mut out = Vec::new();
6313 if let Some(refname) = &options.refname {
6314 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6315 out.push(format!("option refname {refname}"));
6316 }
6317 if let Some(old_oid) = &options.old_oid {
6318 out.push(format!("option old-oid {old_oid}"));
6319 }
6320 if let Some(new_oid) = &options.new_oid {
6321 out.push(format!("option new-oid {new_oid}"));
6322 }
6323 if options.forced_update {
6324 out.push("option forced-update".into());
6325 }
6326 Ok(out)
6327}
6328
6329fn validate_receive_pack_status_message(label: &str, message: &str) -> Result<()> {
6330 if message.is_empty() {
6331 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6332 }
6333 if message
6334 .bytes()
6335 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6336 {
6337 return Err(GitError::InvalidFormat(format!(
6338 "{label} contains a delimiter byte"
6339 )));
6340 }
6341 Ok(())
6342}
6343
6344fn validate_receive_pack_push_option(option: &[u8]) -> Result<()> {
6345 if option.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6346 return Err(GitError::InvalidFormat(
6347 "receive-pack push-option contains a delimiter byte".into(),
6348 ));
6349 }
6350 Ok(())
6351}
6352
6353fn validate_protocol_error_message(message: &str) -> Result<()> {
6354 if message.is_empty() {
6355 return Err(GitError::InvalidFormat(
6356 "protocol error message is empty".into(),
6357 ));
6358 }
6359 if message
6360 .bytes()
6361 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6362 {
6363 return Err(GitError::InvalidFormat(
6364 "protocol error message contains a delimiter byte".into(),
6365 ));
6366 }
6367 Ok(())
6368}
6369
6370fn parse_capability_token(token: &str) -> Result<Capability> {
6371 if token.is_empty() {
6372 return Err(GitError::InvalidFormat("empty capability token".into()));
6373 }
6374 let (name, value) = token
6375 .split_once('=')
6376 .map_or((token, None), |(name, value)| (name, Some(value)));
6377 validate_capability_field("capability name", name)?;
6378 if let Some(value) = value {
6379 validate_capability_field("capability value", value)?;
6380 }
6381 Ok(Capability {
6382 name: name.to_string(),
6383 value: value.map(str::to_string),
6384 })
6385}
6386
6387fn parse_protocol_v2_capability_line(payload: &[u8]) -> Result<Capability> {
6388 let payload = trim_trailing_lf(payload);
6389 if payload.is_empty() {
6390 return Err(GitError::InvalidFormat(
6391 "empty protocol v2 capability line".into(),
6392 ));
6393 }
6394 let text =
6395 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6396 let (name, value) = text
6397 .split_once('=')
6398 .map_or((text, None), |(name, value)| (name, Some(value)));
6399 validate_capability_name(name)?;
6400 if let Some(value) = value {
6401 validate_protocol_v2_capability_value(value)?;
6402 }
6403 Ok(Capability {
6404 name: name.to_string(),
6405 value: value.map(str::to_string),
6406 })
6407}
6408
6409fn parse_protocol_v2_command_line(payload: &[u8]) -> Result<String> {
6410 let payload = trim_trailing_lf(payload);
6411 let text =
6412 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6413 let Some(command) = text.strip_prefix("command=") else {
6414 return Err(GitError::InvalidFormat(
6415 "protocol v2 command request missing command prefix".into(),
6416 ));
6417 };
6418 validate_capability_name(command)?;
6419 Ok(command.to_string())
6420}
6421
6422fn parse_protocol_v2_ls_refs_line(
6423 format: ObjectFormat,
6424 payload: &[u8],
6425) -> Result<ProtocolV2LsRefsRecord> {
6426 let payload = trim_trailing_lf(payload);
6427 if payload.is_empty() {
6428 return Err(GitError::InvalidFormat(
6429 "ls-refs response line is empty".into(),
6430 ));
6431 }
6432 let text =
6433 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6434 let mut fields = text.split(' ');
6435 let first = fields
6436 .next()
6437 .ok_or_else(|| GitError::InvalidFormat("ls-refs response line is empty".into()))?;
6438 if first == "unborn" {
6439 let name = fields
6440 .next()
6441 .ok_or_else(|| GitError::InvalidFormat("ls-refs unborn line is missing name".into()))?;
6442 validate_protocol_v2_token("ls-refs ref name", name)?;
6443 let (symref_target, attributes) = parse_protocol_v2_ls_refs_attributes(format, fields)?;
6444 return Ok(ProtocolV2LsRefsRecord::Unborn {
6445 name: name.to_string(),
6446 symref_target,
6447 attributes,
6448 });
6449 }
6450
6451 let oid = ObjectId::from_hex(format, first)?;
6452 let name = fields
6453 .next()
6454 .ok_or_else(|| GitError::InvalidFormat("ls-refs ref line is missing name".into()))?;
6455 validate_protocol_v2_token("ls-refs ref name", name)?;
6456 let (peeled, symref_target, attributes) =
6457 parse_protocol_v2_ls_refs_ref_attributes(format, fields)?;
6458 Ok(ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
6459 oid,
6460 name: name.to_string(),
6461 peeled,
6462 symref_target,
6463 attributes,
6464 }))
6465}
6466
6467fn parse_protocol_v2_ls_refs_ref_attributes<'a>(
6468 format: ObjectFormat,
6469 fields: impl Iterator<Item = &'a str>,
6470) -> Result<(Option<ObjectId>, Option<String>, Vec<String>)> {
6471 let mut peeled = None;
6472 let (symref_target, attributes) =
6473 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6474 if let Some(value) = attr.strip_prefix("peeled:") {
6475 if peeled.is_some() {
6476 return Err(GitError::InvalidFormat(
6477 "ls-refs response has duplicate peeled attribute".into(),
6478 ));
6479 }
6480 peeled = Some(ObjectId::from_hex(format, value)?);
6481 return Ok(true);
6482 }
6483 Ok(false)
6484 })?;
6485 Ok((peeled, symref_target, attributes))
6486}
6487
6488fn parse_protocol_v2_ls_refs_attributes<'a>(
6489 format: ObjectFormat,
6490 fields: impl Iterator<Item = &'a str>,
6491) -> Result<(Option<String>, Vec<String>)> {
6492 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6493 if attr.starts_with("peeled:") {
6494 return Err(GitError::InvalidFormat(
6495 "ls-refs unborn line has peeled attribute".into(),
6496 ));
6497 }
6498 Ok(false)
6499 })
6500}
6501
6502fn parse_protocol_v2_ls_refs_attributes_with<'a, F>(
6503 _format: ObjectFormat,
6504 fields: impl Iterator<Item = &'a str>,
6505 mut handle_known: F,
6506) -> Result<(Option<String>, Vec<String>)>
6507where
6508 F: FnMut(&str) -> Result<bool>,
6509{
6510 let mut symref_target = None;
6511 let mut attributes = Vec::new();
6512 for attr in fields {
6513 validate_protocol_v2_token("ls-refs attribute", attr)?;
6514 if let Some(value) = attr.strip_prefix("symref-target:") {
6515 if symref_target.is_some() {
6516 return Err(GitError::InvalidFormat(
6517 "ls-refs response has duplicate symref-target attribute".into(),
6518 ));
6519 }
6520 validate_protocol_v2_token("ls-refs symref-target", value)?;
6521 symref_target = Some(value.to_string());
6522 } else if !handle_known(attr)? {
6523 attributes.push(attr.to_string());
6524 }
6525 }
6526 Ok((symref_target, attributes))
6527}
6528
6529fn format_protocol_v2_ls_refs_record(record: &ProtocolV2LsRefsRecord) -> Result<String> {
6530 let mut out = String::new();
6531 match record {
6532 ProtocolV2LsRefsRecord::Ref(reference) => {
6533 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
6534 out.push_str(&reference.oid.to_string());
6535 out.push(' ');
6536 out.push_str(&reference.name);
6537 if let Some(peeled) = &reference.peeled {
6538 if peeled.format() != reference.oid.format() {
6539 return Err(GitError::InvalidObjectId(
6540 "ls-refs peeled object format does not match ref object format".into(),
6541 ));
6542 }
6543 out.push(' ');
6544 out.push_str("peeled:");
6545 out.push_str(&peeled.to_string());
6546 }
6547 if let Some(target) = &reference.symref_target {
6548 validate_protocol_v2_token("ls-refs symref-target", target)?;
6549 out.push(' ');
6550 out.push_str("symref-target:");
6551 out.push_str(target);
6552 }
6553 append_protocol_v2_ls_refs_attributes(&mut out, &reference.attributes)?;
6554 }
6555 ProtocolV2LsRefsRecord::Unborn {
6556 name,
6557 symref_target,
6558 attributes,
6559 } => {
6560 validate_protocol_v2_token("ls-refs ref name", name)?;
6561 out.push_str("unborn ");
6562 out.push_str(name);
6563 if let Some(target) = symref_target {
6564 validate_protocol_v2_token("ls-refs symref-target", target)?;
6565 out.push(' ');
6566 out.push_str("symref-target:");
6567 out.push_str(target);
6568 }
6569 append_protocol_v2_ls_refs_attributes(&mut out, attributes)?;
6570 }
6571 }
6572 Ok(out)
6573}
6574
6575fn append_protocol_v2_ls_refs_attributes(out: &mut String, attributes: &[String]) -> Result<()> {
6576 for attr in attributes {
6577 validate_protocol_v2_token("ls-refs attribute", attr)?;
6578 if attr.starts_with("peeled:") || attr.starts_with("symref-target:") {
6579 return Err(GitError::InvalidFormat(
6580 "ls-refs generic attributes must not duplicate known attributes".into(),
6581 ));
6582 }
6583 out.push(' ');
6584 out.push_str(attr);
6585 }
6586 Ok(())
6587}
6588
6589fn parse_fetch_section_header(payload: &[u8]) -> Result<String> {
6590 let name = parse_protocol_v2_line_text("fetch response section", payload)?;
6591 validate_capability_name(name)?;
6592 Ok(name.to_string())
6593}
6594
6595fn flush_terminates_protocol_v2_response(frames: &[PktLineFrame], idx: usize) -> bool {
6596 idx + 1 == frames.len()
6597 || (idx + 2 == frames.len() && matches!(frames[idx + 1], PktLineFrame::ResponseEnd))
6598}
6599
6600fn parse_fetch_section(
6601 format: ObjectFormat,
6602 name: String,
6603 lines: Vec<Vec<u8>>,
6604) -> Result<ProtocolV2FetchResponseSection> {
6605 match name.as_str() {
6606 "acknowledgments" => lines
6607 .iter()
6608 .map(|line| parse_fetch_acknowledgment(format, line))
6609 .collect::<Result<Vec<_>>>()
6610 .map(ProtocolV2FetchResponseSection::Acknowledgments),
6611 "shallow-info" => lines
6612 .iter()
6613 .map(|line| parse_fetch_shallow_info(format, line))
6614 .collect::<Result<Vec<_>>>()
6615 .map(ProtocolV2FetchResponseSection::ShallowInfo),
6616 "wanted-refs" => lines
6617 .iter()
6618 .map(|line| parse_fetch_wanted_ref(format, line))
6619 .collect::<Result<Vec<_>>>()
6620 .map(ProtocolV2FetchResponseSection::WantedRefs),
6621 "packfile-uris" => lines
6622 .iter()
6623 .map(|line| parse_fetch_packfile_uri(format, line))
6624 .collect::<Result<Vec<_>>>()
6625 .map(ProtocolV2FetchResponseSection::PackfileUris),
6626 "packfile" => Ok(ProtocolV2FetchResponseSection::Packfile(lines)),
6627 _ => Ok(ProtocolV2FetchResponseSection::Unknown { name, lines }),
6628 }
6629}
6630
6631fn parse_fetch_acknowledgment(
6632 format: ObjectFormat,
6633 line: &[u8],
6634) -> Result<ProtocolV2FetchAcknowledgment> {
6635 let text = parse_protocol_v2_line_text("fetch acknowledgment", line)?;
6636 match text {
6637 "NAK" => Ok(ProtocolV2FetchAcknowledgment::Nak),
6638 "ready" => Ok(ProtocolV2FetchAcknowledgment::Ready),
6639 value if value.starts_with("ACK ") => Ok(ProtocolV2FetchAcknowledgment::Ack(
6640 parse_oid_argument(format, "fetch ACK", value, "ACK ")?,
6641 )),
6642 other => Err(GitError::InvalidFormat(format!(
6643 "unsupported fetch acknowledgment {other}"
6644 ))),
6645 }
6646}
6647
6648fn parse_fetch_shallow_info(
6649 format: ObjectFormat,
6650 line: &[u8],
6651) -> Result<ProtocolV2FetchShallowInfo> {
6652 let text = parse_protocol_v2_line_text("fetch shallow-info", line)?;
6653 if text.starts_with("shallow ") {
6654 return Ok(ProtocolV2FetchShallowInfo::Shallow(parse_oid_argument(
6655 format,
6656 "fetch shallow",
6657 text,
6658 "shallow ",
6659 )?));
6660 }
6661 if text.starts_with("unshallow ") {
6662 return Ok(ProtocolV2FetchShallowInfo::Unshallow(parse_oid_argument(
6663 format,
6664 "fetch unshallow",
6665 text,
6666 "unshallow ",
6667 )?));
6668 }
6669 Err(GitError::InvalidFormat(format!(
6670 "unsupported fetch shallow-info {text}"
6671 )))
6672}
6673
6674fn parse_fetch_wanted_ref(format: ObjectFormat, line: &[u8]) -> Result<ProtocolV2FetchWantedRef> {
6675 let text = parse_protocol_v2_line_text("fetch wanted-ref", line)?;
6676 let (oid, name) = text
6677 .split_once(' ')
6678 .ok_or_else(|| GitError::InvalidFormat("fetch wanted-ref is missing name".into()))?;
6679 validate_protocol_v2_token("fetch wanted-ref name", name)?;
6680 Ok(ProtocolV2FetchWantedRef {
6681 oid: ObjectId::from_hex(format, oid)?,
6682 name: name.to_string(),
6683 })
6684}
6685
6686fn parse_fetch_packfile_uri(
6687 format: ObjectFormat,
6688 line: &[u8],
6689) -> Result<ProtocolV2FetchPackfileUri> {
6690 let text = parse_protocol_v2_line_text("fetch packfile-uri", line)?;
6691 let (pack_hash, uri) = text
6692 .split_once(' ')
6693 .ok_or_else(|| GitError::InvalidFormat("fetch packfile-uri is missing uri".into()))?;
6694 validate_protocol_v2_token("fetch packfile-uri hash", pack_hash)?;
6695 validate_protocol_v2_token("fetch packfile-uri", uri)?;
6696 Ok(ProtocolV2FetchPackfileUri {
6697 pack_hash: ObjectId::from_hex(format, pack_hash)?,
6698 uri: uri.to_string(),
6699 })
6700}
6701
6702fn protocol_v2_fetch_section_name(section: &ProtocolV2FetchResponseSection) -> &str {
6703 match section {
6704 ProtocolV2FetchResponseSection::Acknowledgments(_) => "acknowledgments",
6705 ProtocolV2FetchResponseSection::ShallowInfo(_) => "shallow-info",
6706 ProtocolV2FetchResponseSection::WantedRefs(_) => "wanted-refs",
6707 ProtocolV2FetchResponseSection::PackfileUris(_) => "packfile-uris",
6708 ProtocolV2FetchResponseSection::Packfile(_) => "packfile",
6709 ProtocolV2FetchResponseSection::Unknown { name, .. } => name,
6710 }
6711}
6712
6713fn format_protocol_v2_fetch_section_lines(
6714 section: &ProtocolV2FetchResponseSection,
6715) -> Result<Vec<Vec<u8>>> {
6716 match section {
6717 ProtocolV2FetchResponseSection::Acknowledgments(acks) => acks
6718 .iter()
6719 .map(|ack| match ack {
6720 ProtocolV2FetchAcknowledgment::Nak => Ok(line_from_str("NAK")),
6721 ProtocolV2FetchAcknowledgment::Ack(oid) => Ok(line_from_str(&format!("ACK {oid}"))),
6722 ProtocolV2FetchAcknowledgment::Ready => Ok(line_from_str("ready")),
6723 })
6724 .collect(),
6725 ProtocolV2FetchResponseSection::ShallowInfo(entries) => entries
6726 .iter()
6727 .map(|entry| match entry {
6728 ProtocolV2FetchShallowInfo::Shallow(oid) => {
6729 Ok(line_from_str(&format!("shallow {oid}")))
6730 }
6731 ProtocolV2FetchShallowInfo::Unshallow(oid) => {
6732 Ok(line_from_str(&format!("unshallow {oid}")))
6733 }
6734 })
6735 .collect(),
6736 ProtocolV2FetchResponseSection::WantedRefs(refs) => refs
6737 .iter()
6738 .map(|wanted| {
6739 validate_protocol_v2_token("fetch wanted-ref name", &wanted.name)?;
6740 Ok(line_from_str(&format!("{} {}", wanted.oid, wanted.name)))
6741 })
6742 .collect(),
6743 ProtocolV2FetchResponseSection::PackfileUris(uris) => uris
6744 .iter()
6745 .map(|packfile_uri| {
6746 validate_protocol_v2_token("fetch packfile-uri", &packfile_uri.uri)?;
6747 Ok(line_from_str(&format!(
6748 "{} {}",
6749 packfile_uri.pack_hash, packfile_uri.uri
6750 )))
6751 })
6752 .collect(),
6753 ProtocolV2FetchResponseSection::Packfile(lines) => Ok(lines.clone()),
6754 ProtocolV2FetchResponseSection::Unknown { name, lines } => {
6755 validate_capability_name(name)?;
6756 for line in lines {
6757 validate_protocol_v2_line("fetch unknown section line", line)?;
6758 }
6759 Ok(lines.clone())
6760 }
6761 }
6762}
6763
6764fn parse_protocol_v2_object_info_record(
6765 format: ObjectFormat,
6766 line: &[u8],
6767) -> Result<ProtocolV2ObjectInfoRecord> {
6768 let text = parse_protocol_v2_line_text("object-info record", line)?;
6769 let mut fields = text.split(' ');
6770 let oid = fields
6771 .next()
6772 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing oid".into()))?;
6773 let size = fields
6774 .next()
6775 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing size".into()))?;
6776 if fields.next().is_some() {
6777 return Err(GitError::InvalidFormat(
6778 "object-info record has too many fields".into(),
6779 ));
6780 }
6781 validate_protocol_v2_token("object-info oid", oid)?;
6782 validate_protocol_v2_token("object-info size", size)?;
6783 let size = size
6784 .parse::<u64>()
6785 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6786 Ok(ProtocolV2ObjectInfoRecord {
6787 oid: ObjectId::from_hex(format, oid)?,
6788 size,
6789 })
6790}
6791
6792fn parse_dumb_http_info_ref_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpRefRecord> {
6793 validate_dumb_http_info_ref_line(line)?;
6794 let line = trim_trailing_lf(line);
6795 let tab = line
6796 .iter()
6797 .position(|byte| *byte == b'\t')
6798 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP ref record is missing name".into()))?;
6799 let (oid, name) = (&line[..tab], &line[tab + 1..]);
6800 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6801 validate_protocol_v2_token("dumb HTTP ref oid", oid)?;
6802 let name = std::str::from_utf8(name).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6803 let (name, peeled) = name
6804 .strip_suffix("^{}")
6805 .map_or((name, false), |name| (name, true));
6806 validate_dumb_http_ref_name(name)?;
6807 Ok(DumbHttpRefRecord {
6808 oid: ObjectId::from_hex(format, oid)?,
6809 name: name.to_string(),
6810 peeled,
6811 })
6812}
6813
6814fn parse_dumb_http_alternate(line: &[u8]) -> Result<String> {
6815 validate_dumb_http_alternate_line(line)?;
6816 let line = trim_trailing_lf(line);
6817 let alternate =
6818 std::str::from_utf8(line).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6819 validate_dumb_http_alternate(alternate)?;
6820 Ok(alternate.to_string())
6821}
6822
6823fn parse_dumb_http_pack_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpPackRecord> {
6824 validate_dumb_http_info_ref_line(line)?;
6825 let line = parse_protocol_v2_line_text("dumb HTTP pack record", line)?;
6826 let pack_name = line
6827 .strip_prefix("P ")
6828 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP pack record must start with P".into()))?;
6829 let hash = pack_name
6830 .strip_prefix("pack-")
6831 .and_then(|value| value.strip_suffix(".pack"))
6832 .ok_or_else(|| GitError::InvalidFormat("invalid dumb HTTP pack name".into()))?;
6833 validate_protocol_v2_token("dumb HTTP pack hash", hash)?;
6834 Ok(DumbHttpPackRecord {
6835 hash: ObjectId::from_hex(format, hash)?,
6836 })
6837}
6838
6839fn encode_protocol_v2_capability(capability: &Capability) -> Result<Vec<u8>> {
6840 validate_capability_name(&capability.name)?;
6841 let mut out = capability.name.as_bytes().to_vec();
6842 if let Some(value) = &capability.value {
6843 validate_protocol_v2_capability_value(value)?;
6844 out.push(b'=');
6845 out.extend_from_slice(value.as_bytes());
6846 }
6847 Ok(out)
6848}
6849
6850fn validate_capability_field(label: &str, value: &str) -> Result<()> {
6851 if value.is_empty() {
6852 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6853 }
6854 if value
6855 .bytes()
6856 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t' | 0))
6857 {
6858 return Err(GitError::InvalidFormat(format!(
6859 "{label} contains a delimiter byte"
6860 )));
6861 }
6862 Ok(())
6863}
6864
6865fn validate_capability_name(value: &str) -> Result<()> {
6866 validate_capability_field("capability name", value)?;
6867 if value.bytes().any(|byte| byte == b'=') {
6868 return Err(GitError::InvalidFormat(
6869 "capability name contains a delimiter byte".into(),
6870 ));
6871 }
6872 Ok(())
6873}
6874
6875fn validate_protocol_v2_capability_value(value: &str) -> Result<()> {
6876 if value.is_empty() {
6877 return Err(GitError::InvalidFormat(
6878 "protocol v2 capability value is empty".into(),
6879 ));
6880 }
6881 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6882 return Err(GitError::InvalidFormat(
6883 "protocol v2 capability value contains a delimiter byte".into(),
6884 ));
6885 }
6886 Ok(())
6887}
6888
6889fn validate_protocol_v2_argument(value: &[u8]) -> Result<()> {
6890 if value.is_empty() {
6891 return Err(GitError::InvalidFormat(
6892 "protocol v2 command argument is empty".into(),
6893 ));
6894 }
6895 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6896 return Err(GitError::InvalidFormat(
6897 "protocol v2 command argument contains a delimiter byte".into(),
6898 ));
6899 }
6900 Ok(())
6901}
6902
6903fn validate_upload_archive_argument(value: &str) -> Result<()> {
6904 if value.is_empty() {
6905 return Err(GitError::InvalidFormat(
6906 "upload-archive argument is empty".into(),
6907 ));
6908 }
6909 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6910 return Err(GitError::InvalidFormat(
6911 "upload-archive argument contains a delimiter byte".into(),
6912 ));
6913 }
6914 Ok(())
6915}
6916
6917fn validate_upload_archive_status_message(value: &str) -> Result<()> {
6918 if value.is_empty() {
6919 return Err(GitError::InvalidFormat(
6920 "upload-archive status message is empty".into(),
6921 ));
6922 }
6923 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6924 return Err(GitError::InvalidFormat(
6925 "upload-archive status message contains a delimiter byte".into(),
6926 ));
6927 }
6928 Ok(())
6929}
6930
6931fn non_empty(value: &str) -> Option<&str> {
6932 (!value.is_empty()).then_some(value)
6933}
6934
6935fn validate_refspec_value(value: &str) -> Result<()> {
6936 if value.is_empty() {
6937 return Err(GitError::InvalidFormat("refspec is empty".into()));
6938 }
6939 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6940 return Err(GitError::InvalidFormat(
6941 "refspec contains a delimiter byte".into(),
6942 ));
6943 }
6944 Ok(())
6945}
6946
6947fn validate_refspec_endpoint(label: &str, value: &str) -> Result<()> {
6948 if value.is_empty() {
6949 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6950 }
6951 if value
6952 .bytes()
6953 .any(|byte| matches!(byte, b':' | b' ' | b'\t' | b'\n' | b'\r' | 0))
6954 {
6955 return Err(GitError::InvalidFormat(format!(
6956 "{label} contains a delimiter byte"
6957 )));
6958 }
6959 Ok(())
6960}
6961
6962fn count_refspec_wildcards(value: &str) -> usize {
6963 value.bytes().filter(|byte| *byte == b'*').count()
6964}
6965
6966fn validate_refspec_shape(refspec: &RefSpec) -> Result<()> {
6967 if refspec.force && refspec.negative {
6968 return Err(GitError::InvalidFormat(
6969 "negative refspec must not be forced".into(),
6970 ));
6971 }
6972 if refspec.negative && refspec.dst.is_some() {
6973 return Err(GitError::InvalidFormat(
6974 "negative refspec must not have a destination".into(),
6975 ));
6976 }
6977 if refspec.negative && refspec.src.is_none() {
6978 return Err(GitError::InvalidFormat(
6979 "negative refspec is missing a source".into(),
6980 ));
6981 }
6982 if refspec.src.is_none() && refspec.dst.is_none() && refspec.negative {
6983 return Err(GitError::InvalidFormat(
6984 "refspec must include a source or destination".into(),
6985 ));
6986 }
6987 if let Some(src) = &refspec.src {
6988 validate_refspec_endpoint("refspec source", src)?;
6989 }
6990 if let Some(dst) = &refspec.dst {
6991 validate_refspec_endpoint("refspec destination", dst)?;
6992 }
6993 let src_pattern_count = refspec
6994 .src
6995 .as_deref()
6996 .map(count_refspec_wildcards)
6997 .unwrap_or(0);
6998 let dst_pattern_count = refspec
6999 .dst
7000 .as_deref()
7001 .map(count_refspec_wildcards)
7002 .unwrap_or(0);
7003 if src_pattern_count > 1 || dst_pattern_count > 1 {
7004 return Err(GitError::InvalidFormat(
7005 "refspec endpoint has too many wildcards".into(),
7006 ));
7007 }
7008 if refspec.dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
7009 return Err(GitError::InvalidFormat(
7010 "refspec wildcard must appear in both source and destination".into(),
7011 ));
7012 }
7013 if refspec.pattern != (src_pattern_count == 1 || dst_pattern_count == 1) {
7014 return Err(GitError::InvalidFormat(
7015 "refspec pattern flag does not match endpoints".into(),
7016 ));
7017 }
7018 Ok(())
7019}
7020
7021fn parse_fetch_head_record(format: ObjectFormat, line: &[u8]) -> Result<FetchHeadRecord> {
7022 validate_fetch_head_line(line)?;
7023 let line = trim_trailing_lf(line);
7024 let mut fields = line.splitn(3, |byte| *byte == b'\t');
7025 let oid = fields
7026 .next()
7027 .ok_or_else(|| GitError::InvalidFormat("FETCH_HEAD record is missing oid".into()))?;
7028 let merge_marker = fields.next().ok_or_else(|| {
7029 GitError::InvalidFormat("FETCH_HEAD record is missing merge marker".into())
7030 })?;
7031 let description = fields.next().ok_or_else(|| {
7032 GitError::InvalidFormat("FETCH_HEAD record is missing description".into())
7033 })?;
7034 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7035 validate_protocol_v2_token("FETCH_HEAD oid", oid)?;
7036 let not_for_merge = match merge_marker {
7037 b"" => false,
7038 b"not-for-merge" => true,
7039 _ => {
7040 return Err(GitError::InvalidFormat(
7041 "FETCH_HEAD record has invalid merge marker".into(),
7042 ));
7043 }
7044 };
7045 let description =
7046 std::str::from_utf8(description).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7047 validate_fetch_head_description_field(description)?;
7048 Ok(FetchHeadRecord {
7049 oid: ObjectId::from_hex(format, oid)?,
7050 not_for_merge,
7051 description: description.to_string(),
7052 })
7053}
7054
7055fn validate_fetch_head_line(value: &[u8]) -> Result<()> {
7056 if value.is_empty() {
7057 return Err(GitError::InvalidFormat("FETCH_HEAD record is empty".into()));
7058 }
7059 if !value.ends_with(b"\n") {
7060 return Err(GitError::InvalidFormat(
7061 "FETCH_HEAD record missing LF".into(),
7062 ));
7063 }
7064 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7065 return Err(GitError::InvalidFormat(
7066 "FETCH_HEAD record contains a delimiter byte".into(),
7067 ));
7068 }
7069 Ok(())
7070}
7071
7072fn validate_fetch_head_description_field(value: &str) -> Result<()> {
7073 if value.is_empty() {
7074 return Err(GitError::InvalidFormat(
7075 "FETCH_HEAD description is empty".into(),
7076 ));
7077 }
7078 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7079 return Err(GitError::InvalidFormat(
7080 "FETCH_HEAD description contains a delimiter byte".into(),
7081 ));
7082 }
7083 Ok(())
7084}
7085
7086fn refspec_is_excluded(negative: &[&RefSpec], source: &str) -> Result<bool> {
7087 for refspec in negative {
7088 if refspec_matches_source(refspec, source)? {
7089 return Ok(true);
7090 }
7091 }
7092 Ok(false)
7093}
7094
7095fn zero_object_id(format: ObjectFormat) -> Result<ObjectId> {
7096 Ok(ObjectId::null(format))
7097}
7098
7099fn local_ref<'a>(refs: &'a [PushSourceRef], name: &str) -> Option<&'a PushSourceRef> {
7100 refs.iter().find(|reference| reference.name == name)
7101}
7102
7103fn remote_ref<'a>(refs: &'a [RefAdvertisement], name: &str) -> Option<&'a RefAdvertisement> {
7104 refs.iter().find(|reference| reference.name == name)
7105}
7106
7107fn validate_push_source_ref(format: ObjectFormat, reference: &PushSourceRef) -> Result<()> {
7108 if reference.oid.format() != format {
7109 return Err(GitError::InvalidObjectId(
7110 "push source ref object format does not match repository".into(),
7111 ));
7112 }
7113 validate_refspec_endpoint("push source ref name", &reference.name)
7114}
7115
7116fn require_receive_pack_feature(advertised: bool, name: &str) -> Result<()> {
7117 if advertised {
7118 Ok(())
7119 } else {
7120 Err(GitError::InvalidFormat(format!(
7121 "receive-pack feature {name} was not advertised"
7122 )))
7123 }
7124}
7125
7126fn validate_smart_http_service(service: GitService) -> Result<()> {
7127 match service {
7128 GitService::UploadPack | GitService::ReceivePack => Ok(()),
7129 GitService::UploadArchive => Err(GitError::InvalidFormat(
7130 "smart HTTP only supports upload-pack and receive-pack services".into(),
7131 )),
7132 }
7133}
7134
7135fn normalize_http_repository_path(path: &str) -> Result<String> {
7136 if path.is_empty() {
7137 return Err(GitError::InvalidFormat(
7138 "smart HTTP repository path is empty".into(),
7139 ));
7140 }
7141 if !path.starts_with('/') {
7142 return Err(GitError::InvalidFormat(
7143 "smart HTTP repository path must start with /".into(),
7144 ));
7145 }
7146 if path
7147 .bytes()
7148 .any(|byte| matches!(byte, b'\n' | b'\r' | 0 | b'?' | b'#'))
7149 {
7150 return Err(GitError::InvalidFormat(
7151 "smart HTTP repository path contains a delimiter byte".into(),
7152 ));
7153 }
7154 let normalized = path.trim_end_matches('/');
7155 Ok(if normalized.is_empty() {
7156 "/".into()
7157 } else {
7158 normalized.to_string()
7159 })
7160}
7161
7162fn dumb_http_pack_resource_path(
7163 repository_path: &str,
7164 hash: &ObjectId,
7165 suffix: &str,
7166) -> Result<String> {
7167 let repository_path = normalize_http_repository_path(repository_path)?;
7168 Ok(format!(
7169 "{repository_path}/objects/pack/pack-{hash}.{suffix}"
7170 ))
7171}
7172
7173fn parse_smart_http_content_type(value: &str, suffix: &str) -> Result<GitService> {
7174 let value = value.trim();
7175 if value.is_empty() {
7176 return Err(GitError::InvalidFormat(
7177 "smart HTTP content type is empty".into(),
7178 ));
7179 }
7180 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7181 return Err(GitError::InvalidFormat(
7182 "smart HTTP content type contains a delimiter byte".into(),
7183 ));
7184 }
7185 let value = value.to_ascii_lowercase();
7186 let service = value
7187 .strip_prefix("application/x-")
7188 .and_then(|value| value.strip_suffix(suffix))
7189 .ok_or_else(|| GitError::InvalidFormat("invalid smart HTTP content type".into()))?;
7190 let service = parse_git_service(service)?;
7191 validate_smart_http_service(service)?;
7192 Ok(service)
7193}
7194
7195fn validate_dumb_http_info_ref_line(value: &[u8]) -> Result<()> {
7196 if value.is_empty() {
7197 return Err(GitError::InvalidFormat(
7198 "dumb HTTP ref record is empty".into(),
7199 ));
7200 }
7201 if !value.ends_with(b"\n") {
7202 return Err(GitError::InvalidFormat(
7203 "dumb HTTP ref record missing LF".into(),
7204 ));
7205 }
7206 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7207 return Err(GitError::InvalidFormat(
7208 "dumb HTTP ref record contains a delimiter byte".into(),
7209 ));
7210 }
7211 Ok(())
7212}
7213
7214fn validate_dumb_http_ref_name(value: &str) -> Result<()> {
7215 validate_protocol_v2_token("dumb HTTP ref name", value)?;
7216 if value.ends_with("^{}") {
7217 return Err(GitError::InvalidFormat(
7218 "dumb HTTP ref name must not include peeled suffix".into(),
7219 ));
7220 }
7221 Ok(())
7222}
7223
7224fn validate_dumb_http_alternate_line(value: &[u8]) -> Result<()> {
7225 if value.is_empty() {
7226 return Err(GitError::InvalidFormat(
7227 "dumb HTTP alternate is empty".into(),
7228 ));
7229 }
7230 if !value.ends_with(b"\n") {
7231 return Err(GitError::InvalidFormat(
7232 "dumb HTTP alternate missing LF".into(),
7233 ));
7234 }
7235 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7236 return Err(GitError::InvalidFormat(
7237 "dumb HTTP alternate contains a delimiter byte".into(),
7238 ));
7239 }
7240 Ok(())
7241}
7242
7243fn validate_dumb_http_alternate(value: &str) -> Result<()> {
7244 if value.is_empty() {
7245 return Err(GitError::InvalidFormat(
7246 "dumb HTTP alternate is empty".into(),
7247 ));
7248 }
7249 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7250 return Err(GitError::InvalidFormat(
7251 "dumb HTTP alternate contains a delimiter byte".into(),
7252 ));
7253 }
7254 Ok(())
7255}
7256
7257fn validate_protocol_v2_token(label: &str, value: &str) -> Result<()> {
7258 if value.is_empty() {
7259 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7260 }
7261 if value
7262 .bytes()
7263 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | 0))
7264 {
7265 return Err(GitError::InvalidFormat(format!(
7266 "{label} contains a delimiter byte"
7267 )));
7268 }
7269 Ok(())
7270}
7271
7272fn validate_protocol_v2_line(label: &str, value: &[u8]) -> Result<()> {
7273 if value.is_empty() {
7274 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7275 }
7276 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7277 return Err(GitError::InvalidFormat(format!(
7278 "{label} contains a delimiter byte"
7279 )));
7280 }
7281 Ok(())
7282}
7283
7284fn parse_protocol_v2_line_text<'a>(label: &str, value: &'a [u8]) -> Result<&'a str> {
7285 validate_protocol_v2_line(label, value)?;
7286 let value = trim_trailing_lf(value);
7287 if value.is_empty() {
7288 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7289 }
7290 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
7291 return Err(GitError::InvalidFormat(format!(
7292 "{label} contains a delimiter byte"
7293 )));
7294 }
7295 std::str::from_utf8(value).map_err(|err| GitError::InvalidFormat(err.to_string()))
7296}
7297
7298fn parse_oid_argument(
7299 format: ObjectFormat,
7300 label: &str,
7301 value: &str,
7302 prefix: &str,
7303) -> Result<ObjectId> {
7304 let oid = value
7305 .strip_prefix(prefix)
7306 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7307 validate_protocol_v2_token(label, oid)?;
7308 ObjectId::from_hex(format, oid)
7309}
7310
7311fn parse_u32_argument(label: &str, value: &str, prefix: &str) -> Result<u32> {
7312 let number = value
7313 .strip_prefix(prefix)
7314 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7315 validate_protocol_v2_token(label, number)?;
7316 let parsed = number
7317 .parse::<u32>()
7318 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7319 if parsed == 0 {
7320 return Err(GitError::InvalidFormat(format!("{label} must be positive")));
7321 }
7322 Ok(parsed)
7323}
7324
7325fn parse_u64_argument(label: &str, value: &str, prefix: &str) -> Result<u64> {
7326 let number = value
7327 .strip_prefix(prefix)
7328 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7329 validate_protocol_v2_token(label, number)?;
7330 number
7331 .parse::<u64>()
7332 .map_err(|err| GitError::InvalidFormat(err.to_string()))
7333}
7334
7335fn line(mut payload: Vec<u8>) -> Vec<u8> {
7336 payload.push(b'\n');
7337 payload
7338}
7339
7340fn line_from_str(payload: &str) -> Vec<u8> {
7341 line(payload.as_bytes().to_vec())
7342}
7343
7344fn trim_trailing_lf(input: &[u8]) -> &[u8] {
7345 input.strip_suffix(b"\n").unwrap_or(input)
7346}
7347
7348#[cfg(test)]
7349mod tests {
7350 use super::*;
7351
7352 #[test]
7353 fn pkt_line_frame_encodes_data_and_control_frames() {
7354 assert_eq!(PktLine(b"hello\n".to_vec()).encode(), b"000ahello\n");
7355 assert_eq!(
7356 PktLineFrame::data(b"hello\n".to_vec())
7357 .expect("test operation should succeed")
7358 .encode(),
7359 b"000ahello\n"
7360 );
7361 assert_eq!(PktLineFrame::Flush.encode(), b"0000");
7362 assert_eq!(PktLineFrame::Delimiter.encode(), b"0001");
7363 assert_eq!(PktLineFrame::ResponseEnd.encode(), b"0002");
7364 assert_eq!(
7365 PktLineFrame::data(b"hello\n".to_vec())
7366 .expect("test operation should succeed")
7367 .try_encode()
7368 .expect("test operation should succeed"),
7369 b"000ahello\n"
7370 );
7371 }
7372
7373 #[test]
7374 fn pkt_line_frame_parses_data_and_control_frames() {
7375 assert_eq!(
7376 PktLineFrame::parse(b"000ahello\n").expect("test operation should succeed"),
7377 (PktLineFrame::Data(b"hello\n".to_vec()), 10)
7378 );
7379 assert_eq!(
7380 PktLineFrame::parse(b"0000").expect("test operation should succeed"),
7381 (PktLineFrame::Flush, 4)
7382 );
7383 assert_eq!(
7384 PktLineFrame::parse(b"0001").expect("test operation should succeed"),
7385 (PktLineFrame::Delimiter, 4)
7386 );
7387 assert_eq!(
7388 PktLineFrame::parse(b"0002").expect("test operation should succeed"),
7389 (PktLineFrame::ResponseEnd, 4)
7390 );
7391 }
7392
7393 #[test]
7394 fn pkt_line_stream_parses_multiple_frames() {
7395 let frames = parse_pkt_line_stream(b"000eversion 2\n00010009done\n0000")
7396 .expect("test operation should succeed");
7397 assert_eq!(
7398 frames,
7399 vec![
7400 PktLineFrame::Data(b"version 2\n".to_vec()),
7401 PktLineFrame::Delimiter,
7402 PktLineFrame::Data(b"done\n".to_vec()),
7403 PktLineFrame::Flush,
7404 ]
7405 );
7406 }
7407
7408 #[test]
7409 fn pkt_line_stream_reads_and_writes_incremental_io() {
7410 let frames = vec![
7411 PktLineFrame::Data(b"version 2\n".to_vec()),
7412 PktLineFrame::Delimiter,
7413 PktLineFrame::Data(b"done\n".to_vec()),
7414 PktLineFrame::Flush,
7415 ];
7416 let mut encoded = Vec::new();
7417 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
7418 assert_eq!(encoded, b"000eversion 2\n00010009done\n0000");
7419 assert_eq!(
7420 read_pkt_line_frames(&mut encoded.as_slice()).expect("test operation should succeed"),
7421 frames
7422 );
7423
7424 let mut empty: &[u8] = b"";
7425 assert_eq!(
7426 read_pkt_line_frame(&mut empty).expect("test operation should succeed"),
7427 None
7428 );
7429 }
7430
7431 #[test]
7432 fn pkt_line_stream_reads_until_control_packets() {
7433 let input = b"000eversion 2\n0000trailing";
7434 let frames = read_pkt_line_frames_until_flush(&mut &input[..])
7435 .expect("test operation should succeed");
7436 assert_eq!(
7437 frames,
7438 vec![
7439 PktLineFrame::Data(b"version 2\n".to_vec()),
7440 PktLineFrame::Flush,
7441 ]
7442 );
7443
7444 let input = b"0009done\n0002next";
7445 let frames = read_pkt_line_frames_until_response_end(&mut &input[..])
7446 .expect("test operation should succeed");
7447 assert_eq!(
7448 frames,
7449 vec![
7450 PktLineFrame::Data(b"done\n".to_vec()),
7451 PktLineFrame::ResponseEnd,
7452 ]
7453 );
7454 assert!(read_pkt_line_frames_until_flush(&mut &b"0009done\n"[..]).is_err());
7455 }
7456
7457 #[test]
7458 fn pkt_line_rejects_invalid_lengths() {
7459 assert!(PktLineFrame::parse(b"000").is_err());
7460 assert!(PktLineFrame::parse(b"0003").is_err());
7461 assert!(PktLineFrame::parse(b"000ahello").is_err());
7462 assert!(PktLineFrame::parse(b"zzzz").is_err());
7463 assert!(read_pkt_line_frame(&mut &b"000"[..]).is_err());
7464 assert!(read_pkt_line_frame(&mut &b"0003"[..]).is_err());
7465 }
7466
7467 #[test]
7468 fn pkt_line_rejects_oversized_data() {
7469 let payload = vec![b'x'; PKT_LINE_MAX_PAYLOAD_LEN + 1];
7470 assert!(PktLineFrame::data(payload.clone()).is_err());
7471 assert!(PktLine(payload.clone()).try_encode().is_err());
7472 assert!(PktLineFrame::Data(payload.clone()).try_encode().is_err());
7473 assert!(write_pkt_line_frame(&mut Vec::new(), &PktLineFrame::Data(payload)).is_err());
7474 assert!(PktLineFrame::parse(b"fff1").is_err());
7475 }
7476
7477 #[test]
7478 fn protocol_error_lines_parse_encode_and_stream() {
7479 let error = parse_error_line(b"ERR remote rejected request\n")
7480 .expect("test operation should succeed");
7481 assert_eq!(
7482 error,
7483 ProtocolErrorLine {
7484 message: "remote rejected request".into(),
7485 }
7486 );
7487 assert_eq!(
7488 encode_error_line(&error).expect("test operation should succeed"),
7489 b"ERR remote rejected request\n"
7490 );
7491 assert_eq!(
7492 parse_error_frame(&PktLineFrame::Data(
7493 b"ERR remote rejected request\n".to_vec()
7494 ))
7495 .expect("test operation should succeed"),
7496 Some(error.clone())
7497 );
7498 assert_eq!(
7499 parse_error_frame(&PktLineFrame::Data(b"NAK\n".to_vec()))
7500 .expect("test operation should succeed"),
7501 None
7502 );
7503
7504 let mut encoded = Vec::new();
7505 write_error_line(&mut encoded, &error).expect("test operation should succeed");
7506 encoded.extend_from_slice(b"tail");
7507 let mut input = encoded.as_slice();
7508 assert_eq!(
7509 read_error_line(&mut input).expect("test operation should succeed"),
7510 error
7511 );
7512 assert_eq!(input, b"tail");
7513 }
7514
7515 #[test]
7516 fn protocol_error_lines_reject_malformed_messages() {
7517 assert!(parse_error_line(b"ERR\n").is_err());
7518 assert!(parse_error_line(b"ERR \n").is_err());
7519 assert!(parse_error_line(b"ERR bad\0message\n").is_err());
7520 assert!(parse_error_line(b"NAK\n").is_err());
7521 assert!(
7522 encode_error_line(&ProtocolErrorLine {
7523 message: "bad\nmessage".into(),
7524 })
7525 .is_err()
7526 );
7527 assert!(read_error_line(&mut &b"0000"[..]).is_err());
7528 }
7529
7530 #[test]
7531 fn refspec_parser_handles_fetch_push_and_negative_forms() {
7532 assert_eq!(
7533 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7534 .expect("test operation should succeed"),
7535 RefSpec {
7536 force: true,
7537 negative: false,
7538 src: Some("refs/heads/*".into()),
7539 dst: Some("refs/remotes/origin/*".into()),
7540 pattern: true,
7541 }
7542 );
7543 assert_eq!(
7544 parse_refspec("refs/heads/main").expect("test operation should succeed"),
7545 RefSpec {
7546 force: false,
7547 negative: false,
7548 src: Some("refs/heads/main".into()),
7549 dst: None,
7550 pattern: false,
7551 }
7552 );
7553 assert_eq!(
7554 parse_refspec(":refs/heads/topic").expect("test operation should succeed"),
7555 RefSpec {
7556 force: false,
7557 negative: false,
7558 src: None,
7559 dst: Some("refs/heads/topic".into()),
7560 pattern: false,
7561 }
7562 );
7563 assert_eq!(
7564 parse_refspec(":").expect("test operation should succeed"),
7565 RefSpec {
7566 force: false,
7567 negative: false,
7568 src: None,
7569 dst: None,
7570 pattern: false,
7571 }
7572 );
7573 assert_eq!(
7574 parse_refspec("^refs/tags/private/*").expect("test operation should succeed"),
7575 RefSpec {
7576 force: false,
7577 negative: true,
7578 src: Some("refs/tags/private/*".into()),
7579 dst: None,
7580 pattern: true,
7581 }
7582 );
7583 }
7584
7585 #[test]
7586 fn refspec_encode_and_map_sources() {
7587 let pattern = parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7588 .expect("test operation should succeed");
7589 assert_eq!(
7590 encode_refspec(&pattern).expect("test operation should succeed"),
7591 "+refs/heads/*:refs/remotes/origin/*"
7592 );
7593 assert!(
7594 refspec_matches_source(&pattern, "refs/heads/main")
7595 .expect("test operation should succeed")
7596 );
7597 assert_eq!(
7598 refspec_map_source(&pattern, "refs/heads/main").expect("test operation should succeed"),
7599 Some("refs/remotes/origin/main".into())
7600 );
7601 assert_eq!(
7602 refspec_map_source(&pattern, "refs/tags/v1").expect("test operation should succeed"),
7603 None
7604 );
7605
7606 let direct = parse_refspec("HEAD:refs/heads/main").expect("test operation should succeed");
7607 assert_eq!(
7608 encode_refspec(&direct).expect("test operation should succeed"),
7609 "HEAD:refs/heads/main"
7610 );
7611 assert_eq!(
7612 refspec_map_source(&direct, "HEAD").expect("test operation should succeed"),
7613 Some("refs/heads/main".into())
7614 );
7615
7616 let delete = parse_refspec(":refs/heads/old").expect("test operation should succeed");
7617 assert_eq!(
7618 encode_refspec(&delete).expect("test operation should succeed"),
7619 ":refs/heads/old"
7620 );
7621 assert_eq!(
7622 refspec_map_source(&delete, "HEAD").expect("test operation should succeed"),
7623 None
7624 );
7625
7626 let matching = parse_refspec(":").expect("test operation should succeed");
7627 assert_eq!(
7628 encode_refspec(&matching).expect("test operation should succeed"),
7629 ":"
7630 );
7631 }
7632
7633 #[test]
7634 fn refspec_parser_rejects_malformed_values() {
7635 assert!(parse_refspec("").is_err());
7636 assert!(parse_refspec("+^refs/heads/main").is_err());
7637 assert!(parse_refspec("^refs/heads/main:refs/remotes/origin/main").is_err());
7638 assert!(parse_refspec("refs/heads/*:refs/remotes/origin/main").is_err());
7639 assert!(parse_refspec("refs/heads/**:refs/remotes/origin/*").is_err());
7640 assert!(parse_refspec("refs/heads/main:refs/remotes/origin/main:extra").is_err());
7641 assert!(parse_refspec("refs/heads/main\n").is_err());
7642 assert!(
7643 encode_refspec(&RefSpec {
7644 force: false,
7645 negative: false,
7646 src: Some("refs/heads/*".into()),
7647 dst: Some("refs/remotes/origin/main".into()),
7648 pattern: true,
7649 })
7650 .is_err()
7651 );
7652 }
7653
7654 #[test]
7655 fn fetch_head_records_parse_encode_and_describe_refs() {
7656 let first = ObjectId::from_hex(
7657 ObjectFormat::Sha1,
7658 "1111111111111111111111111111111111111111",
7659 )
7660 .expect("test operation should succeed");
7661 let second = ObjectId::from_hex(
7662 ObjectFormat::Sha1,
7663 "2222222222222222222222222222222222222222",
7664 )
7665 .expect("test operation should succeed");
7666 let input = b"1111111111111111111111111111111111111111\t\tbranch 'main' of ../bundle.bdl\n2222222222222222222222222222222222222222\tnot-for-merge\ttag 'v1' of ../bundle.bdl\n";
7667 let records =
7668 parse_fetch_head(ObjectFormat::Sha1, input).expect("test operation should succeed");
7669 assert_eq!(
7670 records,
7671 vec![
7672 FetchHeadRecord {
7673 oid: first,
7674 not_for_merge: false,
7675 description: "branch 'main' of ../bundle.bdl".into(),
7676 },
7677 FetchHeadRecord {
7678 oid: second,
7679 not_for_merge: true,
7680 description: "tag 'v1' of ../bundle.bdl".into(),
7681 },
7682 ]
7683 );
7684 assert_eq!(
7685 encode_fetch_head(&records).expect("test operation should succeed"),
7686 input
7687 );
7688 assert_eq!(
7689 parse_fetch_head(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
7690 Vec::<FetchHeadRecord>::new()
7691 );
7692 assert_eq!(
7693 fetch_head_remote_description("refs/heads/main", "../bundle.bdl")
7694 .expect("test operation should succeed"),
7695 "branch 'main' of ../bundle.bdl"
7696 );
7697 assert_eq!(
7698 fetch_head_remote_description("refs/tags/v1", "../bundle.bdl")
7699 .expect("test operation should succeed"),
7700 "tag 'v1' of ../bundle.bdl"
7701 );
7702 assert_eq!(
7704 fetch_head_remote_description("HEAD", "../bundle.bdl")
7705 .expect("test operation should succeed"),
7706 "../bundle.bdl"
7707 );
7708 }
7709
7710 #[test]
7711 fn fetch_head_records_streams_round_trip() {
7712 let records = vec![FetchHeadRecord {
7713 oid: ObjectId::from_hex(
7714 ObjectFormat::Sha1,
7715 "1111111111111111111111111111111111111111",
7716 )
7717 .expect("test operation should succeed"),
7718 not_for_merge: false,
7719 description: "branch 'main' of ../bundle.bdl".into(),
7720 }];
7721 let mut encoded = Vec::new();
7722 write_fetch_head(&mut encoded, &records).expect("test operation should succeed");
7723 let mut input = encoded.as_slice();
7724 assert_eq!(
7725 read_fetch_head(ObjectFormat::Sha1, &mut input).expect("test operation should succeed"),
7726 records
7727 );
7728 assert!(input.is_empty());
7729 }
7730
7731 #[test]
7732 fn fetch_head_records_reject_malformed_lines() {
7733 assert!(
7734 parse_fetch_head(
7735 ObjectFormat::Sha1,
7736 b"1111111111111111111111111111111111111111\t\tbranch 'main'"
7737 )
7738 .is_err()
7739 );
7740 assert!(
7741 parse_fetch_head(
7742 ObjectFormat::Sha1,
7743 b"1111111111111111111111111111111111111111\tfor-merge\tbranch 'main'\n"
7744 )
7745 .is_err()
7746 );
7747 assert!(parse_fetch_head(ObjectFormat::Sha1, b"not-a-hash\t\tbranch 'main'\n").is_err());
7748 assert!(
7749 encode_fetch_head(&[FetchHeadRecord {
7750 oid: ObjectId::from_hex(
7751 ObjectFormat::Sha1,
7752 "1111111111111111111111111111111111111111"
7753 )
7754 .expect("test operation should succeed"),
7755 not_for_merge: false,
7756 description: "bad\ndescription".into(),
7757 }])
7758 .is_err()
7759 );
7760 }
7761
7762 #[test]
7763 fn fetch_planner_maps_direct_pattern_and_negative_refspecs() {
7764 let main = ObjectId::from_hex(
7765 ObjectFormat::Sha1,
7766 "1111111111111111111111111111111111111111",
7767 )
7768 .expect("test operation should succeed");
7769 let next = ObjectId::from_hex(
7770 ObjectFormat::Sha1,
7771 "2222222222222222222222222222222222222222",
7772 )
7773 .expect("test operation should succeed");
7774 let refs = vec![
7775 RefAdvertisement {
7776 oid: main.clone(),
7777 name: "refs/heads/main".into(),
7778 capabilities: Vec::new(),
7779 },
7780 RefAdvertisement {
7781 oid: next.clone(),
7782 name: "refs/heads/tmp".into(),
7783 capabilities: Vec::new(),
7784 },
7785 ];
7786 let refspecs = vec![
7787 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7788 .expect("test operation should succeed"),
7789 parse_refspec("^refs/heads/tmp").expect("test operation should succeed"),
7790 ];
7791 assert_eq!(
7792 plan_fetch_ref_updates(&refs, &refspecs, false).expect("test operation should succeed"),
7793 vec![FetchRefUpdate {
7794 src: "refs/heads/main".into(),
7795 dst: Some("refs/remotes/origin/main".into()),
7796 oid: main,
7797 not_for_merge: false,
7798 }]
7799 );
7800 }
7801
7802 #[test]
7803 fn fetch_planner_autofollows_tags_and_builds_fetch_head_records() {
7804 let commit = ObjectId::from_hex(
7805 ObjectFormat::Sha1,
7806 "1111111111111111111111111111111111111111",
7807 )
7808 .expect("test operation should succeed");
7809 let refs = vec![
7810 RefAdvertisement {
7811 oid: commit.clone(),
7812 name: "refs/heads/main".into(),
7813 capabilities: Vec::new(),
7814 },
7815 RefAdvertisement {
7816 oid: commit.clone(),
7817 name: "refs/tags/v1".into(),
7818 capabilities: Vec::new(),
7819 },
7820 ];
7821 let refspecs = vec![
7822 parse_refspec("refs/heads/main:refs/heads/main")
7823 .expect("test operation should succeed"),
7824 ];
7825 let updates =
7826 plan_fetch_ref_updates(&refs, &refspecs, true).expect("test operation should succeed");
7827 assert_eq!(
7828 updates,
7829 vec![
7830 FetchRefUpdate {
7831 src: "refs/heads/main".into(),
7832 dst: Some("refs/heads/main".into()),
7833 oid: commit.clone(),
7834 not_for_merge: false,
7835 },
7836 FetchRefUpdate {
7837 src: "refs/tags/v1".into(),
7838 dst: Some("refs/tags/v1".into()),
7839 oid: commit.clone(),
7840 not_for_merge: true,
7841 },
7842 ]
7843 );
7844 assert_eq!(
7845 fetch_ref_updates_to_fetch_head(&updates, "../bundle.bdl")
7846 .expect("test operation should succeed"),
7847 vec![
7848 FetchHeadRecord {
7849 oid: commit.clone(),
7850 not_for_merge: false,
7851 description: "branch 'main' of ../bundle.bdl".into(),
7852 },
7853 FetchHeadRecord {
7854 oid: commit,
7855 not_for_merge: true,
7856 description: "tag 'v1' of ../bundle.bdl".into(),
7857 },
7858 ]
7859 );
7860 }
7861
7862 #[test]
7863 fn fetch_planner_rejects_missing_or_sourceless_refspecs() {
7864 let refs = vec![RefAdvertisement {
7865 oid: ObjectId::from_hex(
7866 ObjectFormat::Sha1,
7867 "1111111111111111111111111111111111111111",
7868 )
7869 .expect("test operation should succeed"),
7870 name: "refs/heads/main".into(),
7871 capabilities: Vec::new(),
7872 }];
7873 assert!(
7874 plan_fetch_ref_updates(
7875 &refs,
7876 &[parse_refspec("refs/heads/missing").expect("test operation should succeed")],
7877 false
7878 )
7879 .is_err()
7880 );
7881 assert!(
7882 plan_fetch_ref_updates(
7883 &refs,
7884 &[parse_refspec(":refs/heads/main").expect("test operation should succeed")],
7885 false
7886 )
7887 .is_err()
7888 );
7889 }
7890
7891 #[test]
7892 fn fetch_planner_sourceless_positive_refspec_returns_err_not_panic() {
7893 let refs = vec![RefAdvertisement {
7898 oid: ObjectId::from_hex(
7899 ObjectFormat::Sha1,
7900 "1111111111111111111111111111111111111111",
7901 )
7902 .expect("test operation should succeed"),
7903 name: "refs/heads/main".into(),
7904 capabilities: Vec::new(),
7905 }];
7906 let malformed = RefSpec {
7907 force: false,
7908 negative: false,
7909 src: None,
7910 dst: Some("refs/heads/main".into()),
7911 pattern: false,
7912 };
7913 let result = plan_fetch_ref_updates(&refs, &[malformed], false);
7914 assert!(
7915 result.is_err(),
7916 "sourceless positive refspec must yield Err, got {result:?}"
7917 );
7918 }
7919
7920 #[test]
7921 fn push_planner_builds_create_update_delete_and_matching_commands() {
7922 let old = ObjectId::from_hex(
7923 ObjectFormat::Sha1,
7924 "1111111111111111111111111111111111111111",
7925 )
7926 .expect("test operation should succeed");
7927 let new = ObjectId::from_hex(
7928 ObjectFormat::Sha1,
7929 "2222222222222222222222222222222222222222",
7930 )
7931 .expect("test operation should succeed");
7932 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
7933 let local_refs = vec![
7934 PushSourceRef {
7935 oid: new.clone(),
7936 name: "refs/heads/main".into(),
7937 },
7938 PushSourceRef {
7939 oid: new.clone(),
7940 name: "refs/heads/new".into(),
7941 },
7942 ];
7943 let remote_refs = vec![
7944 RefAdvertisement {
7945 oid: old.clone(),
7946 name: "refs/heads/main".into(),
7947 capabilities: Vec::new(),
7948 },
7949 RefAdvertisement {
7950 oid: old.clone(),
7951 name: "refs/heads/old".into(),
7952 capabilities: Vec::new(),
7953 },
7954 ];
7955
7956 assert_eq!(
7957 plan_push_commands(
7958 ObjectFormat::Sha1,
7959 &local_refs,
7960 &remote_refs,
7961 &[parse_refspec("refs/heads/main").expect("test operation should succeed")],
7962 )
7963 .expect("test operation should succeed"),
7964 vec![ReceivePackCommand {
7965 old_id: old.clone(),
7966 new_id: new.clone(),
7967 name: "refs/heads/main".into(),
7968 }]
7969 );
7970 assert_eq!(
7971 plan_push_commands(
7972 ObjectFormat::Sha1,
7973 &local_refs,
7974 &remote_refs,
7975 &[parse_refspec("refs/heads/new:refs/heads/new")
7976 .expect("test operation should succeed")],
7977 )
7978 .expect("test operation should succeed"),
7979 vec![ReceivePackCommand {
7980 old_id: zero.clone(),
7981 new_id: new.clone(),
7982 name: "refs/heads/new".into(),
7983 }]
7984 );
7985 assert_eq!(
7986 plan_push_commands(
7987 ObjectFormat::Sha1,
7988 &local_refs,
7989 &remote_refs,
7990 &[parse_refspec(":refs/heads/old").expect("test operation should succeed")],
7991 )
7992 .expect("test operation should succeed"),
7993 vec![ReceivePackCommand {
7994 old_id: old.clone(),
7995 new_id: zero,
7996 name: "refs/heads/old".into(),
7997 }]
7998 );
7999 assert_eq!(
8000 plan_push_commands(
8001 ObjectFormat::Sha1,
8002 &local_refs,
8003 &remote_refs,
8004 &[parse_refspec(":").expect("test operation should succeed")],
8005 )
8006 .expect("test operation should succeed"),
8007 vec![ReceivePackCommand {
8008 old_id: old,
8009 new_id: new,
8010 name: "refs/heads/main".into(),
8011 }]
8012 );
8013 }
8014
8015 #[test]
8016 fn push_planner_builds_wildcard_commands_and_rejects_bad_refspecs() {
8017 let new = ObjectId::from_hex(
8018 ObjectFormat::Sha1,
8019 "2222222222222222222222222222222222222222",
8020 )
8021 .expect("test operation should succeed");
8022 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8023 let local_refs = vec![PushSourceRef {
8024 oid: new.clone(),
8025 name: "refs/heads/topic".into(),
8026 }];
8027 let commands = plan_push_commands(
8028 ObjectFormat::Sha1,
8029 &local_refs,
8030 &[],
8031 &[parse_refspec("refs/heads/*:refs/heads/review/*")
8032 .expect("test operation should succeed")],
8033 )
8034 .expect("test operation should succeed");
8035 assert_eq!(
8036 commands,
8037 vec![ReceivePackCommand {
8038 old_id: zero,
8039 new_id: new,
8040 name: "refs/heads/review/topic".into(),
8041 }]
8042 );
8043 assert!(
8044 plan_push_commands(
8045 ObjectFormat::Sha1,
8046 &local_refs,
8047 &[],
8048 &[parse_refspec("^refs/heads/topic").expect("test operation should succeed")],
8049 )
8050 .is_err()
8051 );
8052 assert!(
8053 plan_push_commands(
8054 ObjectFormat::Sha1,
8055 &local_refs,
8056 &[],
8057 &[parse_refspec(":refs/heads/missing").expect("test operation should succeed")],
8058 )
8059 .is_err()
8060 );
8061 }
8062
8063 #[test]
8064 fn receive_pack_push_request_builder_negotiates_capabilities() {
8065 let old_id = ObjectId::from_hex(
8066 ObjectFormat::Sha1,
8067 "1111111111111111111111111111111111111111",
8068 )
8069 .expect("test operation should succeed");
8070 let new_id = ObjectId::from_hex(
8071 ObjectFormat::Sha1,
8072 "2222222222222222222222222222222222222222",
8073 )
8074 .expect("test operation should succeed");
8075 let features = ReceivePackFeatures {
8076 report_status_v2: true,
8077 atomic: true,
8078 ofs_delta: true,
8079 push_options: true,
8080 side_band_64k: true,
8081 quiet: true,
8082 object_format: Some(ObjectFormat::Sha1),
8083 ..ReceivePackFeatures::default()
8084 };
8085 let request = build_receive_pack_push_request(
8086 &features,
8087 vec![ReceivePackCommand {
8088 old_id,
8089 new_id,
8090 name: "refs/heads/main".into(),
8091 }],
8092 b"PACKdata".to_vec(),
8093 ReceivePackPushRequestOptions {
8094 report_status_v2: true,
8095 atomic: true,
8096 ofs_delta: true,
8097 side_band_64k: true,
8098 quiet: true,
8099 agent: Some("sley/0".into()),
8100 object_format: Some(ObjectFormat::Sha1),
8101 push_options: vec!["ci.skip".into()],
8102 ..ReceivePackPushRequestOptions::default()
8103 },
8104 )
8105 .expect("test operation should succeed");
8106 assert_eq!(
8107 request.commands.capabilities,
8108 vec![
8109 Capability {
8110 name: "report-status-v2".into(),
8111 value: None,
8112 },
8113 Capability {
8114 name: "atomic".into(),
8115 value: None,
8116 },
8117 Capability {
8118 name: "ofs-delta".into(),
8119 value: None,
8120 },
8121 Capability {
8122 name: "side-band-64k".into(),
8123 value: None,
8124 },
8125 Capability {
8126 name: "quiet".into(),
8127 value: None,
8128 },
8129 Capability {
8130 name: "agent".into(),
8131 value: Some("sley/0".into()),
8132 },
8133 Capability {
8134 name: "object-format".into(),
8135 value: Some("sha1".into()),
8136 },
8137 Capability {
8138 name: "push-options".into(),
8139 value: None,
8140 },
8141 ]
8142 );
8143 assert_eq!(request.push_options, Some(vec!["ci.skip".into()]));
8144 validate_receive_pack_push_request_features(&features, &request)
8145 .expect("test operation should succeed");
8146 }
8147
8148 #[test]
8149 fn receive_pack_push_request_builder_handles_delete_only_and_rejects_unadvertised() {
8150 let old_id = ObjectId::from_hex(
8151 ObjectFormat::Sha1,
8152 "1111111111111111111111111111111111111111",
8153 )
8154 .expect("test operation should succeed");
8155 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
8156 let features = ReceivePackFeatures {
8157 delete_refs: true,
8158 ..ReceivePackFeatures::default()
8159 };
8160 let request = build_receive_pack_push_request(
8161 &features,
8162 vec![ReceivePackCommand {
8163 old_id,
8164 new_id: zero,
8165 name: "refs/heads/old".into(),
8166 }],
8167 Vec::new(),
8168 ReceivePackPushRequestOptions::default(),
8169 )
8170 .expect("test operation should succeed");
8171 assert_eq!(
8172 request.commands.capabilities,
8173 vec![Capability {
8174 name: "delete-refs".into(),
8175 value: None,
8176 }]
8177 );
8178 assert!(request.packfile.is_empty());
8179
8180 assert!(
8181 build_receive_pack_push_request(
8182 &ReceivePackFeatures::default(),
8183 request.commands.commands.clone(),
8184 Vec::new(),
8185 ReceivePackPushRequestOptions::default(),
8186 )
8187 .is_err()
8188 );
8189 assert!(
8190 build_receive_pack_push_request(
8191 &features,
8192 request.commands.commands,
8193 b"PACK".to_vec(),
8194 ReceivePackPushRequestOptions::default(),
8195 )
8196 .is_err()
8197 );
8198 assert!(
8199 build_receive_pack_push_request(
8200 &features,
8201 Vec::new(),
8202 Vec::new(),
8203 ReceivePackPushRequestOptions {
8204 push_options: vec!["ci.skip".into()],
8205 ..ReceivePackPushRequestOptions::default()
8206 },
8207 )
8208 .is_err()
8209 );
8210 }
8211
8212 #[test]
8213 fn smart_http_helpers_build_paths_and_content_types() {
8214 let sha1 = ObjectId::from_hex(
8215 ObjectFormat::Sha1,
8216 "1111111111111111111111111111111111111111",
8217 )
8218 .expect("test operation should succeed");
8219 let sha256 = ObjectId::from_hex(
8220 ObjectFormat::Sha256,
8221 "2222222222222222222222222222222222222222222222222222222222222222",
8222 )
8223 .expect("test operation should succeed");
8224 assert_eq!(
8225 smart_http_info_refs_path("/repo.git/", GitService::UploadPack)
8226 .expect("test operation should succeed"),
8227 "/repo.git/info/refs?service=git-upload-pack"
8228 );
8229 assert_eq!(
8230 dumb_http_info_refs_path("/repo.git/").expect("test operation should succeed"),
8231 "/repo.git/info/refs"
8232 );
8233 assert_eq!(
8234 dumb_http_alternates_path("/repo.git").expect("test operation should succeed"),
8235 "/repo.git/objects/info/http-alternates"
8236 );
8237 assert_eq!(
8238 dumb_http_packs_path("/repo.git/").expect("test operation should succeed"),
8239 "/repo.git/objects/info/packs"
8240 );
8241 assert_eq!(
8242 dumb_http_loose_object_path("/repo.git/", &sha1)
8243 .expect("test operation should succeed"),
8244 "/repo.git/objects/11/11111111111111111111111111111111111111"
8245 );
8246 assert_eq!(
8247 dumb_http_loose_object_path("/repo.git/", &sha256)
8248 .expect("test operation should succeed"),
8249 "/repo.git/objects/22/22222222222222222222222222222222222222222222222222222222222222"
8250 );
8251 assert_eq!(
8252 dumb_http_pack_file_path("/repo.git/", &sha1).expect("test operation should succeed"),
8253 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.pack"
8254 );
8255 assert_eq!(
8256 dumb_http_pack_index_path("/repo.git/", &sha1).expect("test operation should succeed"),
8257 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.idx"
8258 );
8259 assert_eq!(
8260 smart_http_rpc_path("/repo.git", GitService::ReceivePack)
8261 .expect("test operation should succeed"),
8262 "/repo.git/git-receive-pack"
8263 );
8264 assert_eq!(
8265 smart_http_advertisement_content_type(GitService::UploadPack)
8266 .expect("test operation should succeed"),
8267 "application/x-git-upload-pack-advertisement"
8268 );
8269 assert_eq!(
8270 smart_http_rpc_request_content_type(GitService::UploadPack)
8271 .expect("test operation should succeed"),
8272 "application/x-git-upload-pack-request"
8273 );
8274 assert_eq!(
8275 smart_http_rpc_result_content_type(GitService::ReceivePack)
8276 .expect("test operation should succeed"),
8277 "application/x-git-receive-pack-result"
8278 );
8279 assert_eq!(
8280 parse_smart_http_advertisement_content_type(
8281 "Application/X-Git-Upload-Pack-Advertisement"
8282 )
8283 .expect("test operation should succeed"),
8284 GitService::UploadPack
8285 );
8286 assert_eq!(
8287 parse_smart_http_rpc_request_content_type("application/x-git-receive-pack-request")
8288 .expect("test operation should succeed"),
8289 GitService::ReceivePack
8290 );
8291 assert_eq!(
8292 parse_smart_http_rpc_result_content_type("application/x-git-upload-pack-result")
8293 .expect("test operation should succeed"),
8294 GitService::UploadPack
8295 );
8296 }
8297
8298 #[test]
8299 fn smart_http_helpers_reject_invalid_services_paths_and_content_types() {
8300 let oid = ObjectId::from_hex(
8301 ObjectFormat::Sha1,
8302 "1111111111111111111111111111111111111111",
8303 )
8304 .expect("test operation should succeed");
8305 assert!(smart_http_info_refs_path("repo.git", GitService::UploadPack).is_err());
8306 assert!(smart_http_rpc_path("/repo.git?x=1", GitService::UploadPack).is_err());
8307 assert!(dumb_http_info_refs_path("repo.git").is_err());
8308 assert!(dumb_http_alternates_path("/repo.git#fragment").is_err());
8309 assert!(dumb_http_packs_path("/repo.git?query").is_err());
8310 assert!(dumb_http_loose_object_path("repo.git", &oid).is_err());
8311 assert!(dumb_http_pack_file_path("/repo.git#fragment", &oid).is_err());
8312 assert!(dumb_http_pack_index_path("/repo.git?query", &oid).is_err());
8313 assert!(smart_http_info_refs_path("/repo.git", GitService::UploadArchive).is_err());
8314 assert!(smart_http_advertisement_content_type(GitService::UploadArchive).is_err());
8315 assert!(
8316 parse_smart_http_advertisement_content_type(
8317 "application/x-git-upload-archive-advertisement"
8318 )
8319 .is_err()
8320 );
8321 assert!(
8322 parse_smart_http_rpc_request_content_type("application/x-git-upload-pack-result")
8323 .is_err()
8324 );
8325 assert!(
8326 parse_smart_http_rpc_result_content_type(
8327 "application/x-git-receive-pack-result; charset=utf-8"
8328 )
8329 .is_err()
8330 );
8331 }
8332
8333 #[test]
8334 fn sideband_packets_parse_and_encode_channels() {
8335 let payloads = vec![
8336 b"\x01PACK bytes".to_vec(),
8337 b"\x02counting objects\n".to_vec(),
8338 b"\x03fatal error\n".to_vec(),
8339 ];
8340 let packets = parse_sideband_packets(&payloads).expect("test operation should succeed");
8341 assert_eq!(
8342 packets,
8343 vec![
8344 SideBandPacket {
8345 channel: SideBandChannel::Data,
8346 data: b"PACK bytes".to_vec(),
8347 },
8348 SideBandPacket {
8349 channel: SideBandChannel::Progress,
8350 data: b"counting objects\n".to_vec(),
8351 },
8352 SideBandPacket {
8353 channel: SideBandChannel::Fatal,
8354 data: b"fatal error\n".to_vec(),
8355 },
8356 ]
8357 );
8358 assert_eq!(
8359 encode_sideband_packets(&packets).expect("test operation should succeed"),
8360 payloads
8361 );
8362 }
8363
8364 #[test]
8365 fn sideband_stream_parses_encodes_and_demuxes_packets() {
8366 let frames = vec![
8367 PktLineFrame::Data(vec![1, b'P', b'A']),
8368 PktLineFrame::Data(vec![2, b'c', b'o', b'u', b'n', b't', b'\n']),
8369 PktLineFrame::Data(vec![1, b'C', b'K']),
8370 PktLineFrame::Flush,
8371 ];
8372 let packets = parse_sideband_stream(&frames).expect("test operation should succeed");
8373 assert_eq!(
8374 packets,
8375 vec![
8376 SideBandPacket {
8377 channel: SideBandChannel::Data,
8378 data: b"PA".to_vec(),
8379 },
8380 SideBandPacket {
8381 channel: SideBandChannel::Progress,
8382 data: b"count\n".to_vec(),
8383 },
8384 SideBandPacket {
8385 channel: SideBandChannel::Data,
8386 data: b"CK".to_vec(),
8387 },
8388 ]
8389 );
8390 assert_eq!(
8391 encode_sideband_stream(&packets).expect("test operation should succeed"),
8392 frames
8393 );
8394 assert_eq!(
8395 demux_sideband_stream(&frames).expect("test operation should succeed"),
8396 SideBandDemux {
8397 data: b"PACK".to_vec(),
8398 progress: vec![b"count\n".to_vec()],
8399 }
8400 );
8401 }
8402
8403 #[test]
8404 fn sideband_stream_reads_and_writes_until_flush() {
8405 let packets = vec![
8406 SideBandPacket {
8407 channel: SideBandChannel::Data,
8408 data: b"PACK".to_vec(),
8409 },
8410 SideBandPacket {
8411 channel: SideBandChannel::Progress,
8412 data: b"done\n".to_vec(),
8413 },
8414 ];
8415 let mut encoded = Vec::new();
8416 write_sideband_stream(&mut encoded, &packets).expect("test operation should succeed");
8417 encoded.extend_from_slice(b"tail");
8418
8419 let mut input = encoded.as_slice();
8420 assert_eq!(
8421 read_sideband_stream(&mut input).expect("test operation should succeed"),
8422 packets
8423 );
8424 assert_eq!(input, b"tail");
8425
8426 let mut input = encoded.as_slice();
8427 assert_eq!(
8428 read_and_demux_sideband_stream(&mut input).expect("test operation should succeed"),
8429 SideBandDemux {
8430 data: b"PACK".to_vec(),
8431 progress: vec![b"done\n".to_vec()],
8432 }
8433 );
8434 assert_eq!(input, b"tail");
8435 }
8436
8437 #[test]
8438 fn sideband_packets_demux_data_and_progress() {
8439 let payloads = vec![
8440 b"\x01PACK".to_vec(),
8441 b"\x02counting objects\n".to_vec(),
8442 b"\x01 bytes".to_vec(),
8443 b"\x02done\n".to_vec(),
8444 ];
8445 assert_eq!(
8446 parse_and_demux_sideband_packets(&payloads).expect("test operation should succeed"),
8447 SideBandDemux {
8448 data: b"PACK bytes".to_vec(),
8449 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
8450 }
8451 );
8452 }
8453
8454 #[test]
8455 fn sideband_packets_reject_bad_channels_and_oversize_payloads() {
8456 assert!(parse_sideband_packet(b"").is_err());
8457 assert!(parse_sideband_packet(b"\x04bad").is_err());
8458 assert!(
8459 parse_sideband_stream(&[PktLineFrame::Data(vec![1, b'P', b'A', b'C', b'K'])]).is_err()
8460 );
8461 assert!(parse_sideband_stream(&[PktLineFrame::Delimiter, PktLineFrame::Flush]).is_err());
8462 assert!(
8463 parse_sideband_stream(&[
8464 PktLineFrame::Data(vec![1, b'P', b'A']),
8465 PktLineFrame::Flush,
8466 PktLineFrame::Data(vec![1, b'C', b'K']),
8467 ])
8468 .is_err()
8469 );
8470 assert!(
8471 parse_sideband_stream(&[
8472 PktLineFrame::Data(vec![1, b'P', b'A']),
8473 PktLineFrame::Data(b"\x04bad".to_vec()),
8474 PktLineFrame::Flush,
8475 ])
8476 .is_err()
8477 );
8478 assert!(
8479 encode_sideband_packet(&SideBandPacket {
8480 channel: SideBandChannel::Data,
8481 data: vec![0; PKT_LINE_MAX_PAYLOAD_LEN],
8482 })
8483 .is_err()
8484 );
8485 assert!(
8486 demux_sideband_packets(&[SideBandPacket {
8487 channel: SideBandChannel::Fatal,
8488 data: b"remote died\n".to_vec(),
8489 }])
8490 .is_err()
8491 );
8492 }
8493
8494 #[test]
8495 fn upload_archive_request_parses_and_encodes_arguments() {
8496 let frames = vec![
8497 PktLineFrame::Data(b"argument --format=tar\n".to_vec()),
8498 PktLineFrame::Data(b"argument HEAD:dir with spaces\n".to_vec()),
8499 PktLineFrame::Flush,
8500 ];
8501 let request = parse_upload_archive_request(&frames).expect("test operation should succeed");
8502 assert_eq!(
8503 request,
8504 UploadArchiveRequest {
8505 arguments: vec!["--format=tar".into(), "HEAD:dir with spaces".into()],
8506 }
8507 );
8508 assert_eq!(
8509 encode_upload_archive_request(&request).expect("test operation should succeed"),
8510 frames
8511 );
8512 }
8513
8514 #[test]
8515 fn upload_archive_request_streams_round_trip() {
8516 let request = UploadArchiveRequest {
8517 arguments: vec!["--prefix=src/".into(), "main".into()],
8518 };
8519 let mut encoded = Vec::new();
8520 write_upload_archive_request(&mut encoded, &request)
8521 .expect("test operation should succeed");
8522 encoded.extend_from_slice(b"tail");
8523
8524 let mut input = encoded.as_slice();
8525 assert_eq!(
8526 read_upload_archive_request(&mut input).expect("test operation should succeed"),
8527 request
8528 );
8529 assert_eq!(input, b"tail");
8530 }
8531
8532 #[test]
8533 fn upload_archive_request_rejects_malformed_streams() {
8534 assert!(parse_upload_archive_request(&[PktLineFrame::Flush]).is_err());
8535 assert!(
8536 parse_upload_archive_request(&[
8537 PktLineFrame::Data(b"--format=tar\n".to_vec()),
8538 PktLineFrame::Flush,
8539 ])
8540 .is_err()
8541 );
8542 assert!(
8543 parse_upload_archive_request(&[
8544 PktLineFrame::Data(b"argument HEAD\n".to_vec()),
8545 PktLineFrame::Delimiter,
8546 PktLineFrame::Flush,
8547 ])
8548 .is_err()
8549 );
8550 assert!(
8551 encode_upload_archive_request(&UploadArchiveRequest {
8552 arguments: vec!["bad\narg".into()],
8553 })
8554 .is_err()
8555 );
8556 }
8557
8558 #[test]
8559 fn upload_archive_response_parses_ack_sideband_and_nack() {
8560 let ack_frames = vec![
8561 PktLineFrame::Data(b"ACK\n".to_vec()),
8562 PktLineFrame::Data(b"\x01tar bytes".to_vec()),
8563 PktLineFrame::Data(b"\x02progress\n".to_vec()),
8564 PktLineFrame::Flush,
8565 ];
8566 let response =
8567 parse_upload_archive_response(&ack_frames).expect("test operation should succeed");
8568 assert_eq!(
8569 response,
8570 UploadArchiveResponse::Ack {
8571 sideband: vec![
8572 SideBandPacket {
8573 channel: SideBandChannel::Data,
8574 data: b"tar bytes".to_vec(),
8575 },
8576 SideBandPacket {
8577 channel: SideBandChannel::Progress,
8578 data: b"progress\n".to_vec(),
8579 },
8580 ],
8581 }
8582 );
8583 assert_eq!(
8584 encode_upload_archive_response(&response).expect("test operation should succeed"),
8585 ack_frames
8586 );
8587 assert_eq!(
8588 demux_upload_archive_response(&response).expect("test operation should succeed"),
8589 SideBandDemux {
8590 data: b"tar bytes".to_vec(),
8591 progress: vec![b"progress\n".to_vec()],
8592 }
8593 );
8594
8595 let nack = UploadArchiveResponse::Nack {
8596 message: "unreachable tree".into(),
8597 };
8598 let nack_frames = vec![
8599 PktLineFrame::Data(b"NACK unreachable tree\n".to_vec()),
8600 PktLineFrame::Flush,
8601 ];
8602 assert_eq!(
8603 parse_upload_archive_response(&nack_frames).expect("test operation should succeed"),
8604 nack
8605 );
8606 assert_eq!(
8607 encode_upload_archive_response(&nack).expect("test operation should succeed"),
8608 nack_frames
8609 );
8610 assert!(demux_upload_archive_response(&nack).is_err());
8611 }
8612
8613 #[test]
8614 fn upload_archive_response_streams_round_trip() {
8615 let response = UploadArchiveResponse::Ack {
8616 sideband: vec![SideBandPacket {
8617 channel: SideBandChannel::Data,
8618 data: b"tar bytes".to_vec(),
8619 }],
8620 };
8621 let mut encoded = Vec::new();
8622 write_upload_archive_response(&mut encoded, &response)
8623 .expect("test operation should succeed");
8624 encoded.extend_from_slice(b"tail");
8625
8626 let mut input = encoded.as_slice();
8627 assert_eq!(
8628 read_upload_archive_response(&mut input).expect("test operation should succeed"),
8629 response
8630 );
8631 assert_eq!(input, b"tail");
8632 }
8633
8634 #[test]
8635 fn upload_archive_response_rejects_malformed_streams() {
8636 assert!(parse_upload_archive_response(&[]).is_err());
8637 assert!(
8638 parse_upload_archive_response(&[
8639 PktLineFrame::Data(b"ACK\n".to_vec()),
8640 PktLineFrame::Flush,
8641 PktLineFrame::Data(b"\x01tail".to_vec()),
8642 ])
8643 .is_err()
8644 );
8645 assert!(
8646 parse_upload_archive_response(&[
8647 PktLineFrame::Data(b"NACK\n".to_vec()),
8648 PktLineFrame::Flush,
8649 ])
8650 .is_err()
8651 );
8652 assert!(
8653 parse_upload_archive_response(&[
8654 PktLineFrame::Data(b"NACK denied\n".to_vec()),
8655 PktLineFrame::Data(b"\x02extra\n".to_vec()),
8656 PktLineFrame::Flush,
8657 ])
8658 .is_err()
8659 );
8660 assert!(
8661 encode_upload_archive_response(&UploadArchiveResponse::Nack {
8662 message: "bad\nmessage".into(),
8663 })
8664 .is_err()
8665 );
8666 }
8667
8668 #[test]
8669 fn capabilities_parse_and_encode_tokens() {
8670 let capabilities = parse_capabilities(
8671 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main\n",
8672 )
8673 .expect("test operation should succeed");
8674 assert_eq!(
8675 capabilities,
8676 vec![
8677 Capability {
8678 name: "multi_ack".into(),
8679 value: None,
8680 },
8681 Capability {
8682 name: "thin-pack".into(),
8683 value: None,
8684 },
8685 Capability {
8686 name: "agent".into(),
8687 value: Some("git/2.54.0".into()),
8688 },
8689 Capability {
8690 name: "symref".into(),
8691 value: Some("HEAD:refs/heads/main".into()),
8692 },
8693 ]
8694 );
8695 assert_eq!(
8696 encode_capabilities(&capabilities).expect("test operation should succeed"),
8697 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main"
8698 );
8699 }
8700
8701 #[test]
8702 fn capabilities_reject_empty_or_delimited_fields() {
8703 assert!(parse_capabilities(b"multi_ack thin-pack").is_err());
8704 assert!(parse_capabilities(b"agent=").is_err());
8705 assert!(parse_capabilities(b"symref=HEAD:refs/heads/main\nbad").is_err());
8706 assert!(
8707 encode_capabilities(&[Capability {
8708 name: "bad name".into(),
8709 value: None,
8710 }])
8711 .is_err()
8712 );
8713 }
8714
8715 #[test]
8716 fn protocol_v2_object_format_uses_capability_or_defaults_to_sha1() {
8717 assert_eq!(
8718 protocol_v2_object_format(&[]).expect("test operation should succeed"),
8719 ObjectFormat::Sha1
8720 );
8721 assert_eq!(
8722 protocol_v2_object_format(&[Capability {
8723 name: "object-format".into(),
8724 value: Some("sha256".into()),
8725 }])
8726 .expect("test operation should succeed"),
8727 ObjectFormat::Sha256
8728 );
8729 assert!(
8730 protocol_v2_object_format(&[Capability {
8731 name: "object-format".into(),
8732 value: None,
8733 }])
8734 .is_err()
8735 );
8736 assert!(
8737 protocol_v2_object_format(&[
8738 Capability {
8739 name: "object-format".into(),
8740 value: Some("sha1".into()),
8741 },
8742 Capability {
8743 name: "object-format".into(),
8744 value: Some("sha256".into()),
8745 },
8746 ])
8747 .is_err()
8748 );
8749 assert!(
8750 protocol_v2_object_format(&[Capability {
8751 name: "object-format".into(),
8752 value: Some("unknown".into()),
8753 }])
8754 .is_err()
8755 );
8756 }
8757
8758 #[test]
8759 fn protocol_v2_command_request_capabilities_validate_against_handshake() {
8760 let handshake = TransportHandshake {
8761 protocol: ProtocolVersion::V2,
8762 capabilities: vec![
8763 Capability {
8764 name: "fetch".into(),
8765 value: Some("shallow filter".into()),
8766 },
8767 Capability {
8768 name: "agent".into(),
8769 value: Some("sley/0".into()),
8770 },
8771 Capability {
8772 name: "object-format".into(),
8773 value: Some("sha1".into()),
8774 },
8775 ],
8776 };
8777 validate_protocol_v2_command_request_capabilities(
8778 &handshake,
8779 &ProtocolV2CommandRequest {
8780 command: "fetch".into(),
8781 capabilities: vec![
8782 Capability {
8783 name: "agent".into(),
8784 value: Some("client/1".into()),
8785 },
8786 Capability {
8787 name: "object-format".into(),
8788 value: Some("sha1".into()),
8789 },
8790 ],
8791 arguments: Vec::new(),
8792 },
8793 )
8794 .expect("test operation should succeed");
8795 assert!(
8796 validate_protocol_v2_command_request_capabilities(
8797 &handshake,
8798 &ProtocolV2CommandRequest {
8799 command: "ls-refs".into(),
8800 capabilities: Vec::new(),
8801 arguments: Vec::new(),
8802 },
8803 )
8804 .is_err()
8805 );
8806 assert!(
8807 validate_protocol_v2_command_request_capabilities(
8808 &handshake,
8809 &ProtocolV2CommandRequest {
8810 command: "fetch".into(),
8811 capabilities: vec![Capability {
8812 name: "server-option".into(),
8813 value: None,
8814 }],
8815 arguments: Vec::new(),
8816 },
8817 )
8818 .is_err()
8819 );
8820 assert!(
8821 validate_protocol_v2_command_request_capabilities(
8822 &handshake,
8823 &ProtocolV2CommandRequest {
8824 command: "fetch".into(),
8825 capabilities: vec![Capability {
8826 name: "object-format".into(),
8827 value: Some("sha256".into()),
8828 }],
8829 arguments: Vec::new(),
8830 },
8831 )
8832 .is_err()
8833 );
8834 assert!(
8835 validate_protocol_v2_command_request_capabilities(
8836 &handshake,
8837 &ProtocolV2CommandRequest {
8838 command: "fetch".into(),
8839 capabilities: vec![Capability {
8840 name: "agent".into(),
8841 value: None,
8842 }],
8843 arguments: Vec::new(),
8844 },
8845 )
8846 .is_err()
8847 );
8848 }
8849
8850 #[test]
8851 fn protocol_v2_command_options_parse_and_encode_known_capabilities() {
8852 let capabilities = vec![
8853 Capability {
8854 name: "agent".into(),
8855 value: Some("sley/0".into()),
8856 },
8857 Capability {
8858 name: "object-format".into(),
8859 value: Some("sha256".into()),
8860 },
8861 Capability {
8862 name: "server-option".into(),
8863 value: Some("trace=true".into()),
8864 },
8865 Capability {
8866 name: "server-option".into(),
8867 value: Some("region=west".into()),
8868 },
8869 Capability {
8870 name: "session-id".into(),
8871 value: Some("abc123".into()),
8872 },
8873 ];
8874 let options = parse_protocol_v2_command_options(&capabilities)
8875 .expect("test operation should succeed");
8876 assert_eq!(
8877 options,
8878 ProtocolV2CommandOptions {
8879 agent: Some("sley/0".into()),
8880 object_format: Some(ObjectFormat::Sha256),
8881 server_options: vec!["trace=true".into(), "region=west".into()],
8882 extra: vec![Capability {
8883 name: "session-id".into(),
8884 value: Some("abc123".into()),
8885 }],
8886 }
8887 );
8888 assert_eq!(
8889 encode_protocol_v2_command_options(&options).expect("test operation should succeed"),
8890 capabilities
8891 );
8892 }
8893
8894 #[test]
8895 fn protocol_v2_command_options_reject_malformed_known_capabilities() {
8896 assert!(
8897 parse_protocol_v2_command_options(&[
8898 Capability {
8899 name: "agent".into(),
8900 value: Some("sley/0".into()),
8901 },
8902 Capability {
8903 name: "agent".into(),
8904 value: Some("sley/1".into()),
8905 },
8906 ])
8907 .is_err()
8908 );
8909 assert!(
8910 parse_protocol_v2_command_options(&[Capability {
8911 name: "object-format".into(),
8912 value: Some("sha512".into()),
8913 }])
8914 .is_err()
8915 );
8916 assert!(
8917 parse_protocol_v2_command_options(&[Capability {
8918 name: "server-option".into(),
8919 value: None,
8920 }])
8921 .is_err()
8922 );
8923 assert!(
8924 encode_protocol_v2_command_options(&ProtocolV2CommandOptions {
8925 extra: vec![Capability {
8926 name: "server-option".into(),
8927 value: Some("trace=true".into()),
8928 }],
8929 ..ProtocolV2CommandOptions::default()
8930 })
8931 .is_err()
8932 );
8933 }
8934
8935 #[test]
8936 fn protocol_v2_ls_refs_features_parse_and_encode_advertisement() {
8937 let capabilities = vec![Capability {
8938 name: "ls-refs".into(),
8939 value: Some("unborn custom".into()),
8940 }];
8941 let features = parse_protocol_v2_ls_refs_features(&capabilities)
8942 .expect("test operation should succeed")
8943 .expect("test operation should succeed");
8944 assert_eq!(
8945 features,
8946 ProtocolV2LsRefsFeatures {
8947 unborn: true,
8948 unknown: vec!["custom".into()],
8949 }
8950 );
8951 assert_eq!(
8952 encode_protocol_v2_ls_refs_capability(&features)
8953 .expect("test operation should succeed"),
8954 capabilities[0]
8955 );
8956 assert_eq!(
8957 parse_protocol_v2_ls_refs_features(&[Capability {
8958 name: "ls-refs".into(),
8959 value: None,
8960 }])
8961 .expect("test operation should succeed")
8962 .expect("test operation should succeed"),
8963 ProtocolV2LsRefsFeatures::default()
8964 );
8965 assert!(
8966 parse_protocol_v2_ls_refs_features(&[Capability {
8967 name: "fetch".into(),
8968 value: Some("filter".into()),
8969 }])
8970 .expect("test operation should succeed")
8971 .is_none()
8972 );
8973 }
8974
8975 #[test]
8976 fn protocol_v2_ls_refs_features_reject_malformed_advertisements() {
8977 assert!(
8978 parse_protocol_v2_ls_refs_features(&[
8979 Capability {
8980 name: "ls-refs".into(),
8981 value: None,
8982 },
8983 Capability {
8984 name: "ls-refs".into(),
8985 value: None,
8986 },
8987 ])
8988 .is_err()
8989 );
8990 assert!(
8991 parse_protocol_v2_ls_refs_features(&[Capability {
8992 name: "ls-refs".into(),
8993 value: Some("unborn custom".into()),
8994 }])
8995 .is_err()
8996 );
8997 assert!(
8998 encode_protocol_v2_ls_refs_capability(&ProtocolV2LsRefsFeatures {
8999 unknown: vec!["unborn".into()],
9000 ..ProtocolV2LsRefsFeatures::default()
9001 })
9002 .is_err()
9003 );
9004 }
9005
9006 #[test]
9007 fn protocol_v2_ls_refs_command_request_validates_unborn_feature() {
9008 let handshake = TransportHandshake {
9009 protocol: ProtocolVersion::V2,
9010 capabilities: vec![Capability {
9011 name: "ls-refs".into(),
9012 value: Some("unborn".into()),
9013 }],
9014 };
9015 let request = ProtocolV2CommandRequest {
9016 command: "ls-refs".into(),
9017 capabilities: Vec::new(),
9018 arguments: vec![b"unborn".to_vec(), b"ref-prefix HEAD".to_vec()],
9019 };
9020 let parsed = validate_protocol_v2_ls_refs_command_request(&handshake, &request)
9021 .expect("test operation should succeed");
9022 assert!(parsed.unborn);
9023 assert_eq!(parsed.ref_prefixes, vec!["HEAD"]);
9024
9025 let blocked = TransportHandshake {
9026 protocol: ProtocolVersion::V2,
9027 capabilities: vec![Capability {
9028 name: "ls-refs".into(),
9029 value: None,
9030 }],
9031 };
9032 assert!(validate_protocol_v2_ls_refs_command_request(&blocked, &request).is_err());
9033 }
9034
9035 #[test]
9036 fn protocol_v2_fetch_features_parse_and_encode_advertisement() {
9037 let capabilities = vec![Capability {
9038 name: "fetch".into(),
9039 value: Some(
9040 "shallow wait-for-done filter ref-in-want sideband-all packfile-uris custom".into(),
9041 ),
9042 }];
9043 let features = parse_protocol_v2_fetch_features(&capabilities)
9044 .expect("test operation should succeed")
9045 .expect("test operation should succeed");
9046 assert_eq!(
9047 features,
9048 ProtocolV2FetchFeatures {
9049 shallow: true,
9050 wait_for_done: true,
9051 filter: true,
9052 ref_in_want: true,
9053 sideband_all: true,
9054 packfile_uris: true,
9055 unknown: vec!["custom".into()],
9056 }
9057 );
9058 assert_eq!(
9059 encode_protocol_v2_fetch_capability(&features).expect("test operation should succeed"),
9060 capabilities[0]
9061 );
9062 assert_eq!(
9063 parse_protocol_v2_fetch_features(&[Capability {
9064 name: "fetch".into(),
9065 value: None,
9066 }])
9067 .expect("test operation should succeed")
9068 .expect("test operation should succeed"),
9069 ProtocolV2FetchFeatures::default()
9070 );
9071 assert!(
9072 parse_protocol_v2_fetch_features(&[])
9073 .expect("test operation should succeed")
9074 .is_none()
9075 );
9076 }
9077
9078 #[test]
9079 fn protocol_v2_fetch_features_reject_malformed_advertisements() {
9080 assert!(
9081 parse_protocol_v2_fetch_features(&[
9082 Capability {
9083 name: "fetch".into(),
9084 value: None,
9085 },
9086 Capability {
9087 name: "fetch".into(),
9088 value: None,
9089 },
9090 ])
9091 .is_err()
9092 );
9093 assert!(
9094 parse_protocol_v2_fetch_features(&[Capability {
9095 name: "fetch".into(),
9096 value: Some("filter shallow".into()),
9097 }])
9098 .is_err()
9099 );
9100 assert!(
9101 encode_protocol_v2_fetch_capability(&ProtocolV2FetchFeatures {
9102 unknown: vec!["filter".into()],
9103 ..ProtocolV2FetchFeatures::default()
9104 })
9105 .is_err()
9106 );
9107 }
9108
9109 #[test]
9110 fn protocol_v2_fetch_request_features_validate_feature_gated_arguments() {
9111 let features = ProtocolV2FetchFeatures {
9112 shallow: true,
9113 wait_for_done: true,
9114 filter: true,
9115 ref_in_want: true,
9116 sideband_all: true,
9117 packfile_uris: true,
9118 unknown: Vec::new(),
9119 };
9120 validate_protocol_v2_fetch_request_features(
9121 &features,
9122 &ProtocolV2FetchRequest {
9123 want_refs: vec!["refs/heads/main".into()],
9124 shallow: vec![
9125 ObjectId::from_hex(
9126 ObjectFormat::Sha1,
9127 "1111111111111111111111111111111111111111",
9128 )
9129 .expect("test operation should succeed"),
9130 ],
9131 deepen: Some(1),
9132 filter: Some("blob:none".into()),
9133 packfile_uris: Some("https".into()),
9134 sideband_all: true,
9135 wait_for_done: true,
9136 ..ProtocolV2FetchRequest::default()
9137 },
9138 )
9139 .expect("test operation should succeed");
9140
9141 let request = ProtocolV2FetchRequest {
9142 want_refs: vec!["refs/heads/main".into()],
9143 filter: Some("blob:none".into()),
9144 sideband_all: true,
9145 ..ProtocolV2FetchRequest::default()
9146 };
9147 assert!(
9148 validate_protocol_v2_fetch_request_features(
9149 &ProtocolV2FetchFeatures::default(),
9150 &request,
9151 )
9152 .is_err()
9153 );
9154 assert!(
9155 validate_protocol_v2_fetch_request_features(
9156 &ProtocolV2FetchFeatures {
9157 ref_in_want: true,
9158 filter: true,
9159 ..ProtocolV2FetchFeatures::default()
9160 },
9161 &request,
9162 )
9163 .is_err()
9164 );
9165 }
9166
9167 #[test]
9168 fn protocol_v2_fetch_command_request_validates_against_handshake_features() {
9169 let handshake = TransportHandshake {
9170 protocol: ProtocolVersion::V2,
9171 capabilities: vec![
9172 Capability {
9173 name: "fetch".into(),
9174 value: Some("filter ref-in-want".into()),
9175 },
9176 Capability {
9177 name: "agent".into(),
9178 value: Some("sley/0".into()),
9179 },
9180 ],
9181 };
9182 let request = ProtocolV2CommandRequest {
9183 command: "fetch".into(),
9184 capabilities: vec![Capability {
9185 name: "agent".into(),
9186 value: Some("client/1".into()),
9187 }],
9188 arguments: vec![
9189 b"want-ref refs/heads/main".to_vec(),
9190 b"filter blob:none".to_vec(),
9191 ],
9192 };
9193 let fetch =
9194 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &request)
9195 .expect("test operation should succeed");
9196 assert_eq!(fetch.want_refs, vec!["refs/heads/main"]);
9197 assert_eq!(fetch.filter.as_deref(), Some("blob:none"));
9198
9199 let mut bad = request.clone();
9200 bad.arguments.push(b"sideband-all".to_vec());
9201 assert!(
9202 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &bad)
9203 .is_err()
9204 );
9205 }
9206
9207 #[test]
9208 fn protocol_v2_object_info_request_parses_encodes_and_validates() {
9209 let oid = ObjectId::from_hex(
9210 ObjectFormat::Sha1,
9211 "1111111111111111111111111111111111111111",
9212 )
9213 .expect("test operation should succeed");
9214 let request = ProtocolV2CommandRequest {
9215 command: "object-info".into(),
9216 capabilities: Vec::new(),
9217 arguments: vec![
9218 b"size".to_vec(),
9219 b"oid 1111111111111111111111111111111111111111".to_vec(),
9220 ],
9221 };
9222 let parsed =
9223 ProtocolV2ObjectInfoRequest::from_command_request(ObjectFormat::Sha1, &request)
9224 .expect("test operation should succeed");
9225 assert_eq!(
9226 parsed,
9227 ProtocolV2ObjectInfoRequest {
9228 size: true,
9229 oids: vec![oid],
9230 }
9231 );
9232 assert_eq!(
9233 parsed
9234 .to_command_request()
9235 .expect("test operation should succeed"),
9236 request
9237 );
9238
9239 let handshake = TransportHandshake {
9240 protocol: ProtocolVersion::V2,
9241 capabilities: vec![Capability {
9242 name: "object-info".into(),
9243 value: None,
9244 }],
9245 };
9246 assert_eq!(
9247 validate_protocol_v2_object_info_command_request(
9248 &handshake,
9249 ObjectFormat::Sha1,
9250 &request,
9251 )
9252 .expect("test operation should succeed"),
9253 parsed
9254 );
9255 }
9256
9257 #[test]
9258 fn protocol_v2_object_info_request_streams_round_trip() {
9259 let request = ProtocolV2ObjectInfoRequest {
9260 size: true,
9261 oids: vec![
9262 ObjectId::from_hex(
9263 ObjectFormat::Sha1,
9264 "1111111111111111111111111111111111111111",
9265 )
9266 .expect("test operation should succeed"),
9267 ],
9268 };
9269 let mut encoded = Vec::new();
9270 write_protocol_v2_object_info_request(&mut encoded, &request)
9271 .expect("test operation should succeed");
9272 encoded.extend_from_slice(b"tail");
9273
9274 let mut input = encoded.as_slice();
9275 assert_eq!(
9276 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut input)
9277 .expect("test operation should succeed"),
9278 request
9279 );
9280 assert_eq!(input, b"tail");
9281 }
9282
9283 #[test]
9284 fn protocol_v2_object_info_request_rejects_malformed_arguments() {
9285 assert!(
9286 ProtocolV2ObjectInfoRequest::from_command_request(
9287 ObjectFormat::Sha1,
9288 &ProtocolV2CommandRequest {
9289 command: "object-info".into(),
9290 capabilities: Vec::new(),
9291 arguments: vec![b"oid 1111111111111111111111111111111111111111".to_vec()],
9292 },
9293 )
9294 .is_err()
9295 );
9296 assert!(
9297 ProtocolV2ObjectInfoRequest::from_command_request(
9298 ObjectFormat::Sha1,
9299 &ProtocolV2CommandRequest {
9300 command: "object-info".into(),
9301 capabilities: Vec::new(),
9302 arguments: vec![b"size".to_vec(), b"size".to_vec()],
9303 },
9304 )
9305 .is_err()
9306 );
9307 assert!(
9308 ProtocolV2ObjectInfoRequest::from_command_request(
9309 ObjectFormat::Sha1,
9310 &ProtocolV2CommandRequest {
9311 command: "object-info".into(),
9312 capabilities: Vec::new(),
9313 arguments: vec![b"size".to_vec()],
9314 },
9315 )
9316 .is_err()
9317 );
9318 assert!(
9319 ProtocolV2ObjectInfoRequest::from_command_request(
9320 ObjectFormat::Sha1,
9321 &ProtocolV2CommandRequest {
9322 command: "object-info".into(),
9323 capabilities: Vec::new(),
9324 arguments: vec![b"size".to_vec(), b"oid not-an-oid".to_vec()],
9325 },
9326 )
9327 .is_err()
9328 );
9329 assert!(
9330 validate_protocol_v2_object_info_command_request(
9331 &TransportHandshake {
9332 protocol: ProtocolVersion::V2,
9333 capabilities: Vec::new(),
9334 },
9335 ObjectFormat::Sha1,
9336 &ProtocolV2CommandRequest {
9337 command: "object-info".into(),
9338 capabilities: Vec::new(),
9339 arguments: vec![
9340 b"size".to_vec(),
9341 b"oid 1111111111111111111111111111111111111111".to_vec(),
9342 ],
9343 },
9344 )
9345 .is_err()
9346 );
9347 }
9348
9349 #[test]
9350 fn protocol_v2_command_request_classifies_known_and_unknown_commands() {
9351 let handshake = TransportHandshake {
9352 protocol: ProtocolVersion::V2,
9353 capabilities: vec![
9354 Capability {
9355 name: "ls-refs".into(),
9356 value: Some("unborn".into()),
9357 },
9358 Capability {
9359 name: "fetch".into(),
9360 value: Some("filter ref-in-want".into()),
9361 },
9362 Capability {
9363 name: "object-info".into(),
9364 value: None,
9365 },
9366 Capability {
9367 name: "server-option".into(),
9368 value: None,
9369 },
9370 Capability {
9371 name: "server-info".into(),
9372 value: Some("custom".into()),
9373 },
9374 ],
9375 };
9376 assert_eq!(
9377 classify_protocol_v2_command_request(
9378 &handshake,
9379 ObjectFormat::Sha1,
9380 &ProtocolV2CommandRequest {
9381 command: "ls-refs".into(),
9382 capabilities: Vec::new(),
9383 arguments: vec![b"unborn".to_vec()],
9384 },
9385 )
9386 .expect("test operation should succeed"),
9387 ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9388 unborn: true,
9389 ..ProtocolV2LsRefsRequest::default()
9390 })
9391 );
9392 assert_eq!(
9393 classify_protocol_v2_command_request(
9394 &handshake,
9395 ObjectFormat::Sha1,
9396 &ProtocolV2CommandRequest {
9397 command: "fetch".into(),
9398 capabilities: Vec::new(),
9399 arguments: vec![
9400 b"want-ref refs/heads/main".to_vec(),
9401 b"filter blob:none".to_vec(),
9402 ],
9403 },
9404 )
9405 .expect("test operation should succeed"),
9406 ProtocolV2Command::Fetch(ProtocolV2FetchRequest {
9407 want_refs: vec!["refs/heads/main".into()],
9408 filter: Some("blob:none".into()),
9409 ..ProtocolV2FetchRequest::default()
9410 })
9411 );
9412 assert_eq!(
9413 classify_protocol_v2_command_request(
9414 &handshake,
9415 ObjectFormat::Sha1,
9416 &ProtocolV2CommandRequest {
9417 command: "object-info".into(),
9418 capabilities: Vec::new(),
9419 arguments: vec![
9420 b"size".to_vec(),
9421 b"oid 1111111111111111111111111111111111111111".to_vec(),
9422 ],
9423 },
9424 )
9425 .expect("test operation should succeed"),
9426 ProtocolV2Command::ObjectInfo(ProtocolV2ObjectInfoRequest {
9427 size: true,
9428 oids: vec![
9429 ObjectId::from_hex(
9430 ObjectFormat::Sha1,
9431 "1111111111111111111111111111111111111111",
9432 )
9433 .expect("test operation should succeed")
9434 ],
9435 })
9436 );
9437
9438 let unknown = ProtocolV2CommandRequest {
9439 command: "server-info".into(),
9440 capabilities: vec![Capability {
9441 name: "server-option".into(),
9442 value: Some("trace=true".into()),
9443 }],
9444 arguments: Vec::new(),
9445 };
9446 assert_eq!(
9447 classify_protocol_v2_command_request(&handshake, ObjectFormat::Sha1, &unknown)
9448 .expect("test operation should succeed"),
9449 ProtocolV2Command::Unknown(unknown)
9450 );
9451 assert!(
9452 classify_protocol_v2_command_request(
9453 &handshake,
9454 ObjectFormat::Sha1,
9455 &ProtocolV2CommandRequest {
9456 command: "not-advertised".into(),
9457 capabilities: Vec::new(),
9458 arguments: Vec::new(),
9459 },
9460 )
9461 .is_err()
9462 );
9463 }
9464
9465 #[test]
9466 fn protocol_v2_session_request_classifies_streamed_command_and_done() {
9467 let handshake = TransportHandshake {
9468 protocol: ProtocolVersion::V2,
9469 capabilities: vec![
9470 Capability {
9471 name: "ls-refs".into(),
9472 value: Some("unborn".into()),
9473 },
9474 Capability {
9475 name: "fetch".into(),
9476 value: Some("filter ref-in-want".into()),
9477 },
9478 ],
9479 };
9480 let command = ProtocolV2Request::Command(ProtocolV2CommandRequest {
9481 command: "ls-refs".into(),
9482 capabilities: Vec::new(),
9483 arguments: vec![b"unborn".to_vec()],
9484 });
9485 assert_eq!(
9486 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &command)
9487 .expect("test operation should succeed"),
9488 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9489 unborn: true,
9490 ..ProtocolV2LsRefsRequest::default()
9491 }))
9492 );
9493 assert_eq!(
9494 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &ProtocolV2Request::Done)
9495 .expect("test operation should succeed"),
9496 ProtocolV2SessionRequest::Done
9497 );
9498
9499 let mut encoded = Vec::new();
9500 write_protocol_v2_request(&mut encoded, &command).expect("test operation should succeed");
9501 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
9502 .expect("test operation should succeed");
9503 encoded.extend_from_slice(b"tail");
9504
9505 let mut input = encoded.as_slice();
9506 assert_eq!(
9507 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9508 .expect("test operation should succeed"),
9509 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9510 unborn: true,
9511 ..ProtocolV2LsRefsRequest::default()
9512 }))
9513 );
9514 assert_eq!(
9515 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9516 .expect("test operation should succeed"),
9517 ProtocolV2SessionRequest::Done
9518 );
9519 assert_eq!(input, b"tail");
9520 }
9521
9522 #[test]
9523 fn advertised_ref_parses_first_v0_capability_line() {
9524 let payload =
9525 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 HEAD\0multi_ack symref=HEAD:refs/heads/main\n";
9526 let advertisement = parse_ref_advertisement(ObjectFormat::Sha1, payload)
9527 .expect("test operation should succeed");
9528 assert_eq!(
9529 advertisement.oid,
9530 ObjectId::from_hex(
9531 ObjectFormat::Sha1,
9532 "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
9533 )
9534 .expect("test operation should succeed")
9535 );
9536 assert_eq!(advertisement.name, "HEAD");
9537 assert_eq!(
9538 advertisement.capabilities,
9539 vec![
9540 Capability {
9541 name: "multi_ack".into(),
9542 value: None,
9543 },
9544 Capability {
9545 name: "symref".into(),
9546 value: Some("HEAD:refs/heads/main".into()),
9547 },
9548 ]
9549 );
9550 }
9551
9552 #[test]
9553 fn advertised_ref_parses_lines_without_capabilities() {
9554 let advertisement = parse_ref_advertisement(
9555 ObjectFormat::Sha1,
9556 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 refs/heads/main\n",
9557 )
9558 .expect("test operation should succeed");
9559 assert_eq!(advertisement.name, "refs/heads/main");
9560 assert!(advertisement.capabilities.is_empty());
9561 }
9562
9563 #[test]
9564 fn advertised_ref_rejects_malformed_payloads() {
9565 assert!(
9566 parse_ref_advertisement(ObjectFormat::Sha1, b"not-an-oid refs/heads/main\n").is_err()
9567 );
9568 assert!(
9569 parse_ref_advertisement(
9570 ObjectFormat::Sha1,
9571 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n"
9572 )
9573 .is_err()
9574 );
9575 }
9576
9577 #[test]
9578 fn advertised_refs_parse_and_encode_stream() {
9579 let main = ObjectId::from_hex(
9580 ObjectFormat::Sha1,
9581 "1111111111111111111111111111111111111111",
9582 )
9583 .expect("test operation should succeed");
9584 let feature = ObjectId::from_hex(
9585 ObjectFormat::Sha1,
9586 "2222222222222222222222222222222222222222",
9587 )
9588 .expect("test operation should succeed");
9589 let frames = vec![
9590 PktLineFrame::Data(
9591 b"1111111111111111111111111111111111111111 HEAD\0multi_ack thin-pack agent=git/2.54.0\n"
9592 .to_vec(),
9593 ),
9594 PktLineFrame::Data(
9595 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9596 ),
9597 PktLineFrame::Flush,
9598 ];
9599 let advertisements = parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9600 .expect("test operation should succeed");
9601 assert_eq!(
9602 advertisements,
9603 vec![
9604 RefAdvertisement {
9605 oid: main,
9606 name: "HEAD".into(),
9607 capabilities: vec![
9608 Capability {
9609 name: "multi_ack".into(),
9610 value: None,
9611 },
9612 Capability {
9613 name: "thin-pack".into(),
9614 value: None,
9615 },
9616 Capability {
9617 name: "agent".into(),
9618 value: Some("git/2.54.0".into()),
9619 },
9620 ],
9621 },
9622 RefAdvertisement {
9623 oid: feature,
9624 name: "refs/heads/feature".into(),
9625 capabilities: Vec::new(),
9626 },
9627 ]
9628 );
9629 assert_eq!(
9630 encode_ref_advertisements(&advertisements).expect("test operation should succeed"),
9631 frames
9632 );
9633 assert_eq!(
9634 parse_ref_advertisements(ObjectFormat::Sha1, &[PktLineFrame::Flush])
9635 .expect("test operation should succeed"),
9636 Vec::<RefAdvertisement>::new()
9637 );
9638 }
9639
9640 #[test]
9641 fn advertised_ref_set_parses_v1_version_refs_and_shallow() {
9642 let main = ObjectId::from_hex(
9643 ObjectFormat::Sha1,
9644 "1111111111111111111111111111111111111111",
9645 )
9646 .expect("test operation should succeed");
9647 let feature = ObjectId::from_hex(
9648 ObjectFormat::Sha1,
9649 "2222222222222222222222222222222222222222",
9650 )
9651 .expect("test operation should succeed");
9652 let shallow = ObjectId::from_hex(
9653 ObjectFormat::Sha1,
9654 "3333333333333333333333333333333333333333",
9655 )
9656 .expect("test operation should succeed");
9657 let frames = vec![
9658 PktLineFrame::Data(b"version 1\n".to_vec()),
9659 PktLineFrame::Data(
9660 b"1111111111111111111111111111111111111111 HEAD\0multi_ack symref=HEAD:refs/heads/main\n"
9661 .to_vec(),
9662 ),
9663 PktLineFrame::Data(
9664 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9665 ),
9666 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
9667 PktLineFrame::Flush,
9668 ];
9669
9670 let set = parse_ref_advertisement_set(ObjectFormat::Sha1, &frames)
9671 .expect("test operation should succeed");
9672 assert_eq!(set.protocol, ProtocolVersion::V1);
9673 assert_eq!(set.shallow, vec![shallow]);
9674 assert_eq!(
9675 set.refs,
9676 vec![
9677 RefAdvertisement {
9678 oid: main,
9679 name: "HEAD".into(),
9680 capabilities: vec![
9681 Capability {
9682 name: "multi_ack".into(),
9683 value: None,
9684 },
9685 Capability {
9686 name: "symref".into(),
9687 value: Some("HEAD:refs/heads/main".into()),
9688 },
9689 ],
9690 },
9691 RefAdvertisement {
9692 oid: feature,
9693 name: "refs/heads/feature".into(),
9694 capabilities: Vec::new(),
9695 },
9696 ]
9697 );
9698 assert_eq!(
9699 parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9700 .expect("test operation should succeed"),
9701 set.refs
9702 );
9703 assert_eq!(
9704 encode_ref_advertisement_set(&set).expect("test operation should succeed"),
9705 frames
9706 );
9707 }
9708
9709 #[test]
9710 fn advertised_refs_streams_round_trip() {
9711 let advertisements = vec![RefAdvertisement {
9712 oid: ObjectId::from_hex(
9713 ObjectFormat::Sha1,
9714 "1111111111111111111111111111111111111111",
9715 )
9716 .expect("test operation should succeed"),
9717 name: "HEAD".into(),
9718 capabilities: vec![Capability {
9719 name: "symref".into(),
9720 value: Some("HEAD:refs/heads/main".into()),
9721 }],
9722 }];
9723 let mut encoded = Vec::new();
9724 write_ref_advertisements(&mut encoded, &advertisements)
9725 .expect("test operation should succeed");
9726 encoded.extend_from_slice(b"tail");
9727
9728 let mut input = encoded.as_slice();
9729 assert_eq!(
9730 read_ref_advertisements(ObjectFormat::Sha1, &mut input)
9731 .expect("test operation should succeed"),
9732 advertisements
9733 );
9734 assert_eq!(input, b"tail");
9735 }
9736
9737 #[test]
9738 fn advertised_ref_set_streams_round_trip() {
9739 let set = RefAdvertisementSet {
9740 protocol: ProtocolVersion::V1,
9741 refs: vec![RefAdvertisement {
9742 oid: ObjectId::from_hex(
9743 ObjectFormat::Sha1,
9744 "1111111111111111111111111111111111111111",
9745 )
9746 .expect("test operation should succeed"),
9747 name: "HEAD".into(),
9748 capabilities: vec![Capability {
9749 name: "symref".into(),
9750 value: Some("HEAD:refs/heads/main".into()),
9751 }],
9752 }],
9753 shallow: vec![
9754 ObjectId::from_hex(
9755 ObjectFormat::Sha1,
9756 "2222222222222222222222222222222222222222",
9757 )
9758 .expect("test operation should succeed"),
9759 ],
9760 };
9761 let mut encoded = Vec::new();
9762 write_ref_advertisement_set(&mut encoded, &set).expect("test operation should succeed");
9763 encoded.extend_from_slice(b"tail");
9764
9765 let mut input = encoded.as_slice();
9766 assert_eq!(
9767 read_ref_advertisement_set(ObjectFormat::Sha1, &mut input)
9768 .expect("test operation should succeed"),
9769 set
9770 );
9771 assert_eq!(input, b"tail");
9772 }
9773
9774 #[test]
9775 fn advertised_refs_reject_malformed_streams() {
9776 assert!(
9777 parse_ref_advertisements(
9778 ObjectFormat::Sha1,
9779 &[PktLineFrame::Data(
9780 b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),
9781 )],
9782 )
9783 .is_err()
9784 );
9785 assert!(
9786 parse_ref_advertisements(
9787 ObjectFormat::Sha1,
9788 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
9789 )
9790 .is_err()
9791 );
9792 assert!(parse_ref_advertisements(
9793 ObjectFormat::Sha1,
9794 &[
9795 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9796 PktLineFrame::Data(
9797 b"2222222222222222222222222222222222222222 refs/heads/main\0thin-pack\n"
9798 .to_vec(),
9799 ),
9800 PktLineFrame::Flush,
9801 ],
9802 )
9803 .is_err());
9804 assert!(parse_ref_advertisement_set(
9805 ObjectFormat::Sha1,
9806 &[
9807 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9808 PktLineFrame::Data(b"version 1\n".to_vec()),
9809 PktLineFrame::Flush,
9810 ],
9811 )
9812 .is_err());
9813 assert!(
9814 parse_ref_advertisement_set(
9815 ObjectFormat::Sha1,
9816 &[
9817 PktLineFrame::Data(b"version 2\n".to_vec()),
9818 PktLineFrame::Flush,
9819 ],
9820 )
9821 .is_err()
9822 );
9823 assert!(
9824 parse_ref_advertisement_set(
9825 ObjectFormat::Sha1,
9826 &[
9827 PktLineFrame::Data(
9828 b"shallow 1111111111111111111111111111111111111111\n".to_vec()
9829 ),
9830 PktLineFrame::Flush,
9831 ],
9832 )
9833 .is_err()
9834 );
9835 assert!(parse_ref_advertisement_set(
9836 ObjectFormat::Sha1,
9837 &[
9838 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9839 PktLineFrame::Data(b"shallow not-an-oid\n".to_vec()),
9840 PktLineFrame::Flush,
9841 ],
9842 )
9843 .is_err());
9844 assert!(parse_ref_advertisement_set(
9845 ObjectFormat::Sha1,
9846 &[
9847 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9848 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
9849 PktLineFrame::Data(
9850 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
9851 ),
9852 PktLineFrame::Flush,
9853 ],
9854 )
9855 .is_err());
9856 assert!(
9857 encode_ref_advertisements(&[
9858 RefAdvertisement {
9859 oid: ObjectId::from_hex(
9860 ObjectFormat::Sha1,
9861 "1111111111111111111111111111111111111111",
9862 )
9863 .expect("test operation should succeed"),
9864 name: "HEAD".into(),
9865 capabilities: Vec::new(),
9866 },
9867 RefAdvertisement {
9868 oid: ObjectId::from_hex(
9869 ObjectFormat::Sha1,
9870 "2222222222222222222222222222222222222222",
9871 )
9872 .expect("test operation should succeed"),
9873 name: "refs/heads/main".into(),
9874 capabilities: vec![Capability {
9875 name: "thin-pack".into(),
9876 value: None,
9877 }],
9878 },
9879 ])
9880 .is_err()
9881 );
9882 assert!(
9883 encode_ref_advertisement(&RefAdvertisement {
9884 oid: ObjectId::from_hex(
9885 ObjectFormat::Sha1,
9886 "1111111111111111111111111111111111111111",
9887 )
9888 .expect("test operation should succeed"),
9889 name: "bad ref".into(),
9890 capabilities: Vec::new(),
9891 })
9892 .is_err()
9893 );
9894 assert!(
9895 encode_ref_advertisement_set(&RefAdvertisementSet {
9896 protocol: ProtocolVersion::V2,
9897 refs: Vec::new(),
9898 shallow: Vec::new(),
9899 })
9900 .is_err()
9901 );
9902 assert!(
9903 encode_ref_advertisement_set(&RefAdvertisementSet {
9904 protocol: ProtocolVersion::V0,
9905 refs: Vec::new(),
9906 shallow: vec![
9907 ObjectId::from_hex(
9908 ObjectFormat::Sha1,
9909 "1111111111111111111111111111111111111111",
9910 )
9911 .expect("test operation should succeed")
9912 ],
9913 })
9914 .is_err()
9915 );
9916 }
9917
9918 #[test]
9919 fn dumb_http_info_refs_parse_and_encode_records() {
9920 let main = ObjectId::from_hex(
9921 ObjectFormat::Sha1,
9922 "1111111111111111111111111111111111111111",
9923 )
9924 .expect("test operation should succeed");
9925 let tag = ObjectId::from_hex(
9926 ObjectFormat::Sha1,
9927 "2222222222222222222222222222222222222222",
9928 )
9929 .expect("test operation should succeed");
9930 let peeled = ObjectId::from_hex(
9931 ObjectFormat::Sha1,
9932 "3333333333333333333333333333333333333333",
9933 )
9934 .expect("test operation should succeed");
9935 let input = b"1111111111111111111111111111111111111111\trefs/heads/main\n2222222222222222222222222222222222222222\trefs/tags/v1.0\n3333333333333333333333333333333333333333\trefs/tags/v1.0^{}\n";
9936
9937 let records = parse_dumb_http_info_refs(ObjectFormat::Sha1, input)
9938 .expect("test operation should succeed");
9939 assert_eq!(
9940 records,
9941 vec![
9942 DumbHttpRefRecord {
9943 oid: main,
9944 name: "refs/heads/main".into(),
9945 peeled: false,
9946 },
9947 DumbHttpRefRecord {
9948 oid: tag,
9949 name: "refs/tags/v1.0".into(),
9950 peeled: false,
9951 },
9952 DumbHttpRefRecord {
9953 oid: peeled,
9954 name: "refs/tags/v1.0".into(),
9955 peeled: true,
9956 },
9957 ]
9958 );
9959 assert_eq!(
9960 encode_dumb_http_info_refs(&records).expect("test operation should succeed"),
9961 input
9962 );
9963 assert_eq!(
9964 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"")
9965 .expect("test operation should succeed"),
9966 Vec::<DumbHttpRefRecord>::new()
9967 );
9968 }
9969
9970 #[test]
9971 fn dumb_http_info_refs_streams_round_trip() {
9972 let records = vec![DumbHttpRefRecord {
9973 oid: ObjectId::from_hex(
9974 ObjectFormat::Sha1,
9975 "1111111111111111111111111111111111111111",
9976 )
9977 .expect("test operation should succeed"),
9978 name: "refs/heads/main".into(),
9979 peeled: false,
9980 }];
9981 let mut encoded = Vec::new();
9982 write_dumb_http_info_refs(&mut encoded, &records).expect("test operation should succeed");
9983 let mut input = encoded.as_slice();
9984 assert_eq!(
9985 read_dumb_http_info_refs(ObjectFormat::Sha1, &mut input)
9986 .expect("test operation should succeed"),
9987 records
9988 );
9989 assert!(input.is_empty());
9990 }
9991
9992 #[test]
9993 fn dumb_http_info_refs_reject_malformed_records() {
9994 assert!(
9995 parse_dumb_http_info_refs(
9996 ObjectFormat::Sha1,
9997 b"1111111111111111111111111111111111111111 refs/heads/main\n",
9998 )
9999 .is_err()
10000 );
10001 assert!(
10002 parse_dumb_http_info_refs(
10003 ObjectFormat::Sha1,
10004 b"1111111111111111111111111111111111111111\trefs/heads/main",
10005 )
10006 .is_err()
10007 );
10008 assert!(
10009 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"not-an-oid\trefs/heads/main\n")
10010 .is_err()
10011 );
10012 assert!(
10013 parse_dumb_http_info_refs(
10014 ObjectFormat::Sha1,
10015 b"1111111111111111111111111111111111111111\tbad ref\n",
10016 )
10017 .is_err()
10018 );
10019 assert!(
10020 encode_dumb_http_info_refs(&[DumbHttpRefRecord {
10021 oid: ObjectId::from_hex(
10022 ObjectFormat::Sha1,
10023 "1111111111111111111111111111111111111111",
10024 )
10025 .expect("test operation should succeed"),
10026 name: "refs/tags/v1.0^{}".into(),
10027 peeled: false,
10028 }])
10029 .is_err()
10030 );
10031 }
10032
10033 #[test]
10034 fn dumb_http_alternates_parse_and_encode_locations() {
10035 let input = b"https://example.com/base.git/objects/\n../other.git/objects/\n";
10036 let alternates = parse_dumb_http_alternates(input).expect("test operation should succeed");
10037 assert_eq!(
10038 alternates,
10039 vec![
10040 "https://example.com/base.git/objects/".to_string(),
10041 "../other.git/objects/".to_string(),
10042 ]
10043 );
10044 assert_eq!(
10045 encode_dumb_http_alternates(&alternates).expect("test operation should succeed"),
10046 input
10047 );
10048 assert_eq!(
10049 parse_dumb_http_alternates(b"").expect("test operation should succeed"),
10050 Vec::<String>::new()
10051 );
10052 }
10053
10054 #[test]
10055 fn dumb_http_alternates_streams_round_trip() {
10056 let alternates = vec!["https://example.com/base.git/objects/".to_string()];
10057 let mut encoded = Vec::new();
10058 write_dumb_http_alternates(&mut encoded, &alternates)
10059 .expect("test operation should succeed");
10060 let mut input = encoded.as_slice();
10061 assert_eq!(
10062 read_dumb_http_alternates(&mut input).expect("test operation should succeed"),
10063 alternates
10064 );
10065 assert!(input.is_empty());
10066 }
10067
10068 #[test]
10069 fn dumb_http_alternates_reject_malformed_lines() {
10070 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/").is_err());
10071 assert!(parse_dumb_http_alternates(b"\n").is_err());
10072 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/\r\n").is_err());
10073 assert!(encode_dumb_http_alternates(&["bad\nalternate".to_string()]).is_err());
10074 }
10075
10076 #[test]
10077 fn dumb_http_packs_parse_and_encode_pack_records() {
10078 let first = ObjectId::from_hex(
10079 ObjectFormat::Sha1,
10080 "1111111111111111111111111111111111111111",
10081 )
10082 .expect("test operation should succeed");
10083 let second = ObjectId::from_hex(
10084 ObjectFormat::Sha1,
10085 "2222222222222222222222222222222222222222",
10086 )
10087 .expect("test operation should succeed");
10088 let input = b"P pack-1111111111111111111111111111111111111111.pack\nP pack-2222222222222222222222222222222222222222.pack\n";
10089 let records = parse_dumb_http_packs(ObjectFormat::Sha1, input)
10090 .expect("test operation should succeed");
10091 assert_eq!(
10092 records,
10093 vec![
10094 DumbHttpPackRecord { hash: first },
10095 DumbHttpPackRecord { hash: second },
10096 ]
10097 );
10098 assert_eq!(
10099 encode_dumb_http_packs(&records).expect("test operation should succeed"),
10100 input
10101 );
10102 assert_eq!(
10103 parse_dumb_http_packs(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
10104 Vec::<DumbHttpPackRecord>::new()
10105 );
10106 }
10107
10108 #[test]
10109 fn dumb_http_packs_streams_round_trip() {
10110 let records = vec![DumbHttpPackRecord {
10111 hash: ObjectId::from_hex(
10112 ObjectFormat::Sha1,
10113 "1111111111111111111111111111111111111111",
10114 )
10115 .expect("test operation should succeed"),
10116 }];
10117 let mut encoded = Vec::new();
10118 write_dumb_http_packs(&mut encoded, &records).expect("test operation should succeed");
10119 let mut input = encoded.as_slice();
10120 assert_eq!(
10121 read_dumb_http_packs(ObjectFormat::Sha1, &mut input)
10122 .expect("test operation should succeed"),
10123 records
10124 );
10125 assert!(input.is_empty());
10126 }
10127
10128 #[test]
10129 fn dumb_http_packs_reject_malformed_records() {
10130 assert!(
10131 parse_dumb_http_packs(
10132 ObjectFormat::Sha1,
10133 b"P pack-1111111111111111111111111111111111111111.pack",
10134 )
10135 .is_err()
10136 );
10137 assert!(
10138 parse_dumb_http_packs(
10139 ObjectFormat::Sha1,
10140 b"pack-1111111111111111111111111111111111111111.pack\n",
10141 )
10142 .is_err()
10143 );
10144 assert!(parse_dumb_http_packs(ObjectFormat::Sha1, b"P pack-not-a-hash.pack\n",).is_err());
10145 assert!(
10146 parse_dumb_http_packs(
10147 ObjectFormat::Sha1,
10148 b"P pack-1111111111111111111111111111111111111111.idx\n",
10149 )
10150 .is_err()
10151 );
10152 }
10153
10154 #[test]
10155 fn upload_pack_features_parse_encode_and_validate_request() {
10156 let capabilities = vec![
10157 Capability {
10158 name: "multi_ack".into(),
10159 value: None,
10160 },
10161 Capability {
10162 name: "multi_ack_detailed".into(),
10163 value: None,
10164 },
10165 Capability {
10166 name: "no-done".into(),
10167 value: None,
10168 },
10169 Capability {
10170 name: "thin-pack".into(),
10171 value: None,
10172 },
10173 Capability {
10174 name: "side-band-64k".into(),
10175 value: None,
10176 },
10177 Capability {
10178 name: "ofs-delta".into(),
10179 value: None,
10180 },
10181 Capability {
10182 name: "shallow".into(),
10183 value: None,
10184 },
10185 Capability {
10186 name: "deepen-since".into(),
10187 value: None,
10188 },
10189 Capability {
10190 name: "deepen-not".into(),
10191 value: None,
10192 },
10193 Capability {
10194 name: "include-tag".into(),
10195 value: None,
10196 },
10197 Capability {
10198 name: "no-progress".into(),
10199 value: None,
10200 },
10201 Capability {
10202 name: "filter".into(),
10203 value: None,
10204 },
10205 Capability {
10206 name: "agent".into(),
10207 value: Some("git/2.54.0".into()),
10208 },
10209 Capability {
10210 name: "object-format".into(),
10211 value: Some("sha256".into()),
10212 },
10213 Capability {
10214 name: "symref".into(),
10215 value: Some("HEAD:refs/heads/main".into()),
10216 },
10217 Capability {
10218 name: "custom".into(),
10219 value: Some("value".into()),
10220 },
10221 ];
10222 let features =
10223 parse_upload_pack_features(&capabilities).expect("test operation should succeed");
10224 assert_eq!(
10225 features,
10226 UploadPackFeatures {
10227 multi_ack: true,
10228 multi_ack_detailed: true,
10229 no_done: true,
10230 thin_pack: true,
10231 side_band_64k: true,
10232 ofs_delta: true,
10233 shallow: true,
10234 deepen_since: true,
10235 deepen_not: true,
10236 include_tag: true,
10237 no_progress: true,
10238 filter: true,
10239 agent: Some("git/2.54.0".into()),
10240 object_format: Some(ObjectFormat::Sha256),
10241 symrefs: vec!["HEAD:refs/heads/main".into()],
10242 unknown: vec![Capability {
10243 name: "custom".into(),
10244 value: Some("value".into()),
10245 }],
10246 ..UploadPackFeatures::default()
10247 }
10248 );
10249 assert_eq!(
10250 encode_upload_pack_features(&features).expect("test operation should succeed"),
10251 capabilities
10252 );
10253
10254 let request = UploadPackRequest {
10255 wants: vec![
10256 ObjectId::from_hex(
10257 ObjectFormat::Sha1,
10258 "1111111111111111111111111111111111111111",
10259 )
10260 .expect("test operation should succeed"),
10261 ],
10262 capabilities: vec![
10263 Capability {
10264 name: "multi_ack_detailed".into(),
10265 value: None,
10266 },
10267 Capability {
10268 name: "thin-pack".into(),
10269 value: None,
10270 },
10271 Capability {
10272 name: "side-band-64k".into(),
10273 value: None,
10274 },
10275 Capability {
10276 name: "ofs-delta".into(),
10277 value: None,
10278 },
10279 Capability {
10280 name: "include-tag".into(),
10281 value: None,
10282 },
10283 Capability {
10284 name: "agent".into(),
10285 value: Some("sley".into()),
10286 },
10287 ],
10288 shallow: vec![
10289 ObjectId::from_hex(
10290 ObjectFormat::Sha1,
10291 "2222222222222222222222222222222222222222",
10292 )
10293 .expect("test operation should succeed"),
10294 ],
10295 deepen: Some(5),
10296 deepen_since: Some(1_710_000_000),
10297 deepen_not: vec!["refs/tags/base".into()],
10298 filter: Some("blob:none".into()),
10299 };
10300 validate_upload_pack_request_features(&features, &request)
10301 .expect("test operation should succeed");
10302 }
10303
10304 #[test]
10305 fn upload_pack_features_reject_invalid_requests() {
10306 let want = ObjectId::from_hex(
10307 ObjectFormat::Sha1,
10308 "1111111111111111111111111111111111111111",
10309 )
10310 .expect("test operation should succeed");
10311 let features = UploadPackFeatures {
10312 thin_pack: true,
10313 side_band: true,
10314 ..UploadPackFeatures::default()
10315 };
10316
10317 assert!(
10318 validate_upload_pack_request_features(
10319 &features,
10320 &UploadPackRequest {
10321 wants: vec![want],
10322 capabilities: vec![Capability {
10323 name: "ofs-delta".into(),
10324 value: None,
10325 }],
10326 ..UploadPackRequest::default()
10327 },
10328 )
10329 .is_err()
10330 );
10331 assert!(
10332 validate_upload_pack_request_features(
10333 &features,
10334 &UploadPackRequest {
10335 wants: vec![want],
10336 shallow: vec![want],
10337 ..UploadPackRequest::default()
10338 },
10339 )
10340 .is_err()
10341 );
10342 assert!(
10343 validate_upload_pack_request_features(
10344 &features,
10345 &UploadPackRequest {
10346 wants: vec![want],
10347 filter: Some("blob:none".into()),
10348 ..UploadPackRequest::default()
10349 },
10350 )
10351 .is_err()
10352 );
10353 assert!(
10354 validate_upload_pack_request_features(
10355 &UploadPackFeatures {
10356 side_band: true,
10357 side_band_64k: true,
10358 ..UploadPackFeatures::default()
10359 },
10360 &UploadPackRequest {
10361 wants: vec![want],
10362 capabilities: vec![
10363 Capability {
10364 name: "side-band".into(),
10365 value: None,
10366 },
10367 Capability {
10368 name: "side-band-64k".into(),
10369 value: None,
10370 },
10371 ],
10372 ..UploadPackRequest::default()
10373 },
10374 )
10375 .is_err()
10376 );
10377
10378 assert!(
10379 parse_upload_pack_features(&[
10380 Capability {
10381 name: "thin-pack".into(),
10382 value: None,
10383 },
10384 Capability {
10385 name: "thin-pack".into(),
10386 value: None,
10387 },
10388 ])
10389 .is_err()
10390 );
10391 assert!(
10392 encode_upload_pack_features(&UploadPackFeatures {
10393 unknown: vec![Capability {
10394 name: "filter".into(),
10395 value: None,
10396 }],
10397 ..UploadPackFeatures::default()
10398 })
10399 .is_err()
10400 );
10401 }
10402
10403 #[test]
10404 fn upload_pack_raw_response_builder_filters_unknown_haves_and_builds_pack() {
10405 let want = ObjectId::from_hex(
10406 ObjectFormat::Sha1,
10407 "1111111111111111111111111111111111111111",
10408 )
10409 .expect("test operation should succeed");
10410 let known_have = ObjectId::from_hex(
10411 ObjectFormat::Sha1,
10412 "2222222222222222222222222222222222222222",
10413 )
10414 .expect("test operation should succeed");
10415 let unknown_have = ObjectId::from_hex(
10416 ObjectFormat::Sha1,
10417 "3333333333333333333333333333333333333333",
10418 )
10419 .expect("test operation should succeed");
10420 let existing = std::collections::HashSet::from([want, known_have]);
10421
10422 let response = build_upload_pack_raw_packfile_response(
10423 &UploadPackFeatures::default(),
10424 UploadPackRequest {
10425 wants: vec![want],
10426 ..UploadPackRequest::default()
10427 },
10428 [known_have, unknown_have],
10429 |oid| Ok(existing.contains(oid)),
10430 |wants, haves| {
10431 assert_eq!(wants, vec![want]);
10432 assert_eq!(haves, vec![known_have]);
10433 Ok(Some(b"PACKmock".to_vec()))
10434 },
10435 )
10436 .expect("test operation should succeed");
10437
10438 assert_eq!(
10439 response.acknowledgments,
10440 vec![UploadPackAcknowledgment::Nak]
10441 );
10442 assert_eq!(response.packfile, b"PACKmock");
10443 }
10444
10445 #[test]
10446 fn upload_pack_raw_response_builder_rejects_missing_want_and_empty_pack() {
10447 let want = ObjectId::from_hex(
10448 ObjectFormat::Sha1,
10449 "1111111111111111111111111111111111111111",
10450 )
10451 .expect("test operation should succeed");
10452
10453 assert!(
10454 build_upload_pack_raw_packfile_response(
10455 &UploadPackFeatures::default(),
10456 UploadPackRequest {
10457 wants: vec![want],
10458 ..UploadPackRequest::default()
10459 },
10460 Vec::<ObjectId>::new(),
10461 |_| Ok(false),
10462 |_, _| Ok(Some(b"PACKmock".to_vec())),
10463 )
10464 .is_err()
10465 );
10466
10467 assert!(
10468 build_upload_pack_raw_packfile_response(
10469 &UploadPackFeatures::default(),
10470 UploadPackRequest {
10471 wants: vec![want],
10472 ..UploadPackRequest::default()
10473 },
10474 Vec::<ObjectId>::new(),
10475 |_| Ok(true),
10476 |_, _| Ok(None),
10477 )
10478 .is_err()
10479 );
10480 }
10481
10482 #[test]
10483 fn upload_pack_request_parses_and_encodes_initial_fetch_request() {
10484 let want = ObjectId::from_hex(
10485 ObjectFormat::Sha1,
10486 "1111111111111111111111111111111111111111",
10487 )
10488 .expect("test operation should succeed");
10489 let second_want = ObjectId::from_hex(
10490 ObjectFormat::Sha1,
10491 "2222222222222222222222222222222222222222",
10492 )
10493 .expect("test operation should succeed");
10494 let shallow = ObjectId::from_hex(
10495 ObjectFormat::Sha1,
10496 "3333333333333333333333333333333333333333",
10497 )
10498 .expect("test operation should succeed");
10499 let frames = vec![
10500 PktLineFrame::Data(
10501 b"want 1111111111111111111111111111111111111111 multi_ack thin-pack agent=git/2.54.0\n"
10502 .to_vec(),
10503 ),
10504 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10505 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
10506 PktLineFrame::Data(b"deepen-since 1710000000\n".to_vec()),
10507 PktLineFrame::Data(b"deepen-not refs/tags/base\n".to_vec()),
10508 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10509 PktLineFrame::Flush,
10510 ];
10511 let request = parse_upload_pack_request(ObjectFormat::Sha1, &frames)
10512 .expect("test operation should succeed")
10513 .expect("test operation should succeed");
10514 assert_eq!(
10515 request,
10516 UploadPackRequest {
10517 wants: vec![want, second_want],
10518 capabilities: vec![
10519 Capability {
10520 name: "multi_ack".into(),
10521 value: None,
10522 },
10523 Capability {
10524 name: "thin-pack".into(),
10525 value: None,
10526 },
10527 Capability {
10528 name: "agent".into(),
10529 value: Some("git/2.54.0".into()),
10530 },
10531 ],
10532 shallow: vec![shallow],
10533 deepen: None,
10534 deepen_since: Some(1_710_000_000),
10535 deepen_not: vec!["refs/tags/base".into()],
10536 filter: Some("blob:none".into()),
10537 }
10538 );
10539 assert_eq!(
10540 encode_upload_pack_request(Some(&request)).expect("test operation should succeed"),
10541 frames
10542 );
10543 assert_eq!(
10544 parse_upload_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10545 .expect("test operation should succeed"),
10546 None
10547 );
10548 assert_eq!(
10549 encode_upload_pack_request(None).expect("test operation should succeed"),
10550 vec![PktLineFrame::Flush]
10551 );
10552 }
10553
10554 #[test]
10555 fn upload_pack_request_streams_round_trip() {
10556 let request = UploadPackRequest {
10557 wants: vec![
10558 ObjectId::from_hex(
10559 ObjectFormat::Sha1,
10560 "1111111111111111111111111111111111111111",
10561 )
10562 .expect("test operation should succeed"),
10563 ],
10564 capabilities: vec![Capability {
10565 name: "ofs-delta".into(),
10566 value: None,
10567 }],
10568 deepen: Some(10),
10569 ..UploadPackRequest::default()
10570 };
10571 let mut encoded = Vec::new();
10572 write_upload_pack_request(&mut encoded, Some(&request))
10573 .expect("test operation should succeed");
10574 encoded.extend_from_slice(b"tail");
10575
10576 let mut input = encoded.as_slice();
10577 assert_eq!(
10578 read_upload_pack_request(ObjectFormat::Sha1, &mut input)
10579 .expect("test operation should succeed"),
10580 Some(request)
10581 );
10582 assert_eq!(input, b"tail");
10583 }
10584
10585 #[test]
10586 fn upload_pack_request_rejects_malformed_requests() {
10587 assert!(
10588 parse_upload_pack_request(
10589 ObjectFormat::Sha1,
10590 &[PktLineFrame::Data(
10591 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10592 )],
10593 )
10594 .is_err()
10595 );
10596 assert!(
10597 parse_upload_pack_request(
10598 ObjectFormat::Sha1,
10599 &[
10600 PktLineFrame::Data(
10601 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10602 ),
10603 PktLineFrame::Flush,
10604 ],
10605 )
10606 .is_err()
10607 );
10608 assert!(
10609 parse_upload_pack_request(
10610 ObjectFormat::Sha1,
10611 &[
10612 PktLineFrame::Data(
10613 b"want 1111111111111111111111111111111111111111 thin-pack\n".to_vec(),
10614 ),
10615 PktLineFrame::Data(
10616 b"want 2222222222222222222222222222222222222222 ofs-delta\n".to_vec(),
10617 ),
10618 PktLineFrame::Flush,
10619 ],
10620 )
10621 .is_err()
10622 );
10623 assert!(parse_upload_pack_request(
10624 ObjectFormat::Sha1,
10625 &[
10626 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10627 PktLineFrame::Data(b"deepen 1\n".to_vec()),
10628 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10629 PktLineFrame::Flush,
10630 ],
10631 )
10632 .is_err());
10633 assert!(parse_upload_pack_request(
10634 ObjectFormat::Sha1,
10635 &[
10636 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10637 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10638 PktLineFrame::Data(b"filter tree:0\n".to_vec()),
10639 PktLineFrame::Flush,
10640 ],
10641 )
10642 .is_err());
10643 assert!(encode_upload_pack_request(Some(&UploadPackRequest::default())).is_err());
10644 assert!(
10645 encode_upload_pack_request(Some(&UploadPackRequest {
10646 wants: vec![
10647 ObjectId::from_hex(
10648 ObjectFormat::Sha1,
10649 "1111111111111111111111111111111111111111",
10650 )
10651 .expect("test operation should succeed")
10652 ],
10653 deepen: Some(0),
10654 ..UploadPackRequest::default()
10655 }))
10656 .is_err()
10657 );
10658 }
10659
10660 #[test]
10661 fn upload_pack_shallow_update_parses_and_encodes_records() {
10662 let shallow = ObjectId::from_hex(
10663 ObjectFormat::Sha1,
10664 "1111111111111111111111111111111111111111",
10665 )
10666 .expect("test operation should succeed");
10667 let unshallow = ObjectId::from_hex(
10668 ObjectFormat::Sha1,
10669 "2222222222222222222222222222222222222222",
10670 )
10671 .expect("test operation should succeed");
10672 let frames = vec![
10673 PktLineFrame::Data(b"shallow 1111111111111111111111111111111111111111\n".to_vec()),
10674 PktLineFrame::Data(b"unshallow 2222222222222222222222222222222222222222\n".to_vec()),
10675 PktLineFrame::Flush,
10676 ];
10677 let entries = parse_upload_pack_shallow_update(ObjectFormat::Sha1, &frames)
10678 .expect("test operation should succeed");
10679 assert_eq!(
10680 entries,
10681 vec![
10682 ProtocolV2FetchShallowInfo::Shallow(shallow),
10683 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
10684 ]
10685 );
10686 assert_eq!(
10687 encode_upload_pack_shallow_update(&entries).expect("test operation should succeed"),
10688 frames
10689 );
10690 assert_eq!(
10691 parse_upload_pack_shallow_update(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10692 .expect("test operation should succeed"),
10693 Vec::<ProtocolV2FetchShallowInfo>::new()
10694 );
10695 }
10696
10697 #[test]
10698 fn upload_pack_shallow_update_streams_round_trip() {
10699 let entries = vec![ProtocolV2FetchShallowInfo::Shallow(
10700 ObjectId::from_hex(
10701 ObjectFormat::Sha1,
10702 "1111111111111111111111111111111111111111",
10703 )
10704 .expect("test operation should succeed"),
10705 )];
10706 let mut encoded = Vec::new();
10707 write_upload_pack_shallow_update(&mut encoded, &entries)
10708 .expect("test operation should succeed");
10709 encoded.extend_from_slice(b"tail");
10710
10711 let mut input = encoded.as_slice();
10712 assert_eq!(
10713 read_upload_pack_shallow_update(ObjectFormat::Sha1, &mut input)
10714 .expect("test operation should succeed"),
10715 entries
10716 );
10717 assert_eq!(input, b"tail");
10718 }
10719
10720 #[test]
10721 fn upload_pack_shallow_update_rejects_malformed_records() {
10722 assert!(
10723 parse_upload_pack_shallow_update(
10724 ObjectFormat::Sha1,
10725 &[PktLineFrame::Data(
10726 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10727 )],
10728 )
10729 .is_err()
10730 );
10731 assert!(
10732 parse_upload_pack_shallow_update(
10733 ObjectFormat::Sha1,
10734 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10735 )
10736 .is_err()
10737 );
10738 assert!(
10739 parse_upload_pack_shallow_update(
10740 ObjectFormat::Sha1,
10741 &[
10742 PktLineFrame::Data(
10743 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10744 ),
10745 PktLineFrame::Flush,
10746 PktLineFrame::Data(
10747 b"unshallow 2222222222222222222222222222222222222222\n".to_vec(),
10748 ),
10749 ],
10750 )
10751 .is_err()
10752 );
10753 assert!(
10754 parse_upload_pack_shallow_update(
10755 ObjectFormat::Sha1,
10756 &[
10757 PktLineFrame::Data(
10758 b"unsupported 1111111111111111111111111111111111111111\n".to_vec(),
10759 ),
10760 PktLineFrame::Flush,
10761 ],
10762 )
10763 .is_err()
10764 );
10765 }
10766
10767 #[test]
10768 fn upload_pack_negotiation_request_parses_flush_and_done_rounds() {
10769 let have = ObjectId::from_hex(
10770 ObjectFormat::Sha1,
10771 "1111111111111111111111111111111111111111",
10772 )
10773 .expect("test operation should succeed");
10774 let second_have = ObjectId::from_hex(
10775 ObjectFormat::Sha1,
10776 "2222222222222222222222222222222222222222",
10777 )
10778 .expect("test operation should succeed");
10779 let flush_round = vec![
10780 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10781 PktLineFrame::Data(b"have 2222222222222222222222222222222222222222\n".to_vec()),
10782 PktLineFrame::Flush,
10783 ];
10784 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &flush_round)
10785 .expect("test operation should succeed");
10786 assert_eq!(
10787 request,
10788 UploadPackNegotiationRequest {
10789 haves: vec![have, second_have],
10790 done: false,
10791 }
10792 );
10793 assert_eq!(
10794 encode_upload_pack_negotiation_request(&request)
10795 .expect("test operation should succeed"),
10796 flush_round
10797 );
10798
10799 let done_round = vec![
10800 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10801 PktLineFrame::Data(b"done\n".to_vec()),
10802 ];
10803 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &done_round)
10804 .expect("test operation should succeed");
10805 assert_eq!(
10806 request,
10807 UploadPackNegotiationRequest {
10808 haves: vec![have],
10809 done: true,
10810 }
10811 );
10812 assert_eq!(
10813 encode_upload_pack_negotiation_request(&request)
10814 .expect("test operation should succeed"),
10815 done_round
10816 );
10817 }
10818
10819 #[test]
10820 fn upload_pack_negotiation_request_streams_round_trip() {
10821 let first = UploadPackNegotiationRequest {
10822 haves: vec![
10823 ObjectId::from_hex(
10824 ObjectFormat::Sha1,
10825 "1111111111111111111111111111111111111111",
10826 )
10827 .expect("test operation should succeed"),
10828 ],
10829 done: false,
10830 };
10831 let second = UploadPackNegotiationRequest {
10832 haves: Vec::new(),
10833 done: true,
10834 };
10835 let mut encoded = Vec::new();
10836 write_upload_pack_negotiation_request(&mut encoded, &first)
10837 .expect("test operation should succeed");
10838 write_upload_pack_negotiation_request(&mut encoded, &second)
10839 .expect("test operation should succeed");
10840 encoded.extend_from_slice(b"tail");
10841
10842 let mut input = encoded.as_slice();
10843 assert_eq!(
10844 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10845 .expect("test operation should succeed"),
10846 first
10847 );
10848 assert_eq!(
10849 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10850 .expect("test operation should succeed"),
10851 second
10852 );
10853 assert_eq!(input, b"tail");
10854 }
10855
10856 #[test]
10857 fn upload_pack_negotiation_request_rejects_malformed_rounds() {
10858 assert!(
10859 parse_upload_pack_negotiation_request(
10860 ObjectFormat::Sha1,
10861 &[PktLineFrame::Data(
10862 b"have 1111111111111111111111111111111111111111\n".to_vec(),
10863 )],
10864 )
10865 .is_err()
10866 );
10867 assert!(
10868 parse_upload_pack_negotiation_request(
10869 ObjectFormat::Sha1,
10870 &[PktLineFrame::Data(
10871 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10872 )],
10873 )
10874 .is_err()
10875 );
10876 assert!(parse_upload_pack_negotiation_request(
10877 ObjectFormat::Sha1,
10878 &[
10879 PktLineFrame::Data(b"done\n".to_vec()),
10880 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec(),),
10881 ],
10882 )
10883 .is_err());
10884 assert!(
10885 parse_upload_pack_negotiation_request(
10886 ObjectFormat::Sha1,
10887 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10888 )
10889 .is_err()
10890 );
10891 }
10892
10893 #[test]
10894 fn upload_pack_acknowledgments_parse_and_encode_statuses() {
10895 let oid = ObjectId::from_hex(
10896 ObjectFormat::Sha1,
10897 "1111111111111111111111111111111111111111",
10898 )
10899 .expect("test operation should succeed");
10900 assert_eq!(
10901 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"NAK\n")
10902 .expect("test operation should succeed"),
10903 UploadPackAcknowledgment::Nak
10904 );
10905 for (payload, status) in [
10906 (
10907 b"ACK 1111111111111111111111111111111111111111\n".as_slice(),
10908 None,
10909 ),
10910 (
10911 b"ACK 1111111111111111111111111111111111111111 continue\n".as_slice(),
10912 Some(UploadPackAckStatus::Continue),
10913 ),
10914 (
10915 b"ACK 1111111111111111111111111111111111111111 common\n".as_slice(),
10916 Some(UploadPackAckStatus::Common),
10917 ),
10918 (
10919 b"ACK 1111111111111111111111111111111111111111 ready\n".as_slice(),
10920 Some(UploadPackAckStatus::Ready),
10921 ),
10922 ] {
10923 let acknowledgment = parse_upload_pack_acknowledgment(ObjectFormat::Sha1, payload)
10924 .expect("test operation should succeed");
10925 assert_eq!(
10926 acknowledgment,
10927 UploadPackAcknowledgment::Ack { oid, status }
10928 );
10929 assert_eq!(
10930 encode_upload_pack_acknowledgment(&acknowledgment)
10931 .expect("test operation should succeed"),
10932 payload
10933 );
10934 }
10935 }
10936
10937 #[test]
10938 fn upload_pack_acknowledgments_stream_round_trip_and_reject_bad_lines() {
10939 let acknowledgment = UploadPackAcknowledgment::Ack {
10940 oid: ObjectId::from_hex(
10941 ObjectFormat::Sha1,
10942 "1111111111111111111111111111111111111111",
10943 )
10944 .expect("test operation should succeed"),
10945 status: Some(UploadPackAckStatus::Ready),
10946 };
10947 let mut encoded = Vec::new();
10948 write_upload_pack_acknowledgment(&mut encoded, &acknowledgment)
10949 .expect("test operation should succeed");
10950 encoded.extend_from_slice(b"tail");
10951
10952 let mut input = encoded.as_slice();
10953 assert_eq!(
10954 read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut input)
10955 .expect("test operation should succeed"),
10956 acknowledgment
10957 );
10958 assert_eq!(input, b"tail");
10959 assert!(parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ACK not-an-oid\n").is_err());
10960 assert!(
10961 parse_upload_pack_acknowledgment(
10962 ObjectFormat::Sha1,
10963 b"ACK 1111111111111111111111111111111111111111 unknown\n",
10964 )
10965 .is_err()
10966 );
10967 assert!(
10968 parse_upload_pack_acknowledgment(
10969 ObjectFormat::Sha1,
10970 b"ACK 1111111111111111111111111111111111111111 ready extra\n",
10971 )
10972 .is_err()
10973 );
10974 assert!(
10975 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ERR remote died\n").is_err()
10976 );
10977 assert!(read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut &b"0000"[..]).is_err());
10978 }
10979
10980 #[test]
10981 fn upload_pack_packfile_response_parses_acknowledgments_and_sideband() {
10982 let oid = ObjectId::from_hex(
10983 ObjectFormat::Sha1,
10984 "1111111111111111111111111111111111111111",
10985 )
10986 .expect("test operation should succeed");
10987 let frames = vec![
10988 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()),
10989 PktLineFrame::Data(b"NAK\n".to_vec()),
10990 PktLineFrame::Data(b"\x01PACK".to_vec()),
10991 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
10992 PktLineFrame::Data(b"\x01 bytes".to_vec()),
10993 PktLineFrame::Flush,
10994 ];
10995 let response = parse_upload_pack_packfile_response(ObjectFormat::Sha1, &frames)
10996 .expect("test operation should succeed");
10997 assert_eq!(
10998 response,
10999 UploadPackPackfileResponse {
11000 acknowledgments: vec![
11001 UploadPackAcknowledgment::Ack {
11002 oid,
11003 status: Some(UploadPackAckStatus::Common),
11004 },
11005 UploadPackAcknowledgment::Nak,
11006 ],
11007 sideband: vec![
11008 SideBandPacket {
11009 channel: SideBandChannel::Data,
11010 data: b"PACK".to_vec(),
11011 },
11012 SideBandPacket {
11013 channel: SideBandChannel::Progress,
11014 data: b"counting objects\n".to_vec(),
11015 },
11016 SideBandPacket {
11017 channel: SideBandChannel::Data,
11018 data: b" bytes".to_vec(),
11019 },
11020 ],
11021 }
11022 );
11023 assert_eq!(
11024 demux_upload_pack_packfile_response(&response).expect("test operation should succeed"),
11025 SideBandDemux {
11026 data: b"PACK bytes".to_vec(),
11027 progress: vec![b"counting objects\n".to_vec()],
11028 }
11029 );
11030 assert_eq!(
11031 encode_upload_pack_packfile_response(&response).expect("test operation should succeed"),
11032 frames
11033 );
11034 }
11035
11036 #[test]
11037 fn upload_pack_packfile_response_streams_round_trip() {
11038 let response = UploadPackPackfileResponse {
11039 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11040 sideband: vec![SideBandPacket {
11041 channel: SideBandChannel::Data,
11042 data: b"PACK".to_vec(),
11043 }],
11044 };
11045 let mut encoded = Vec::new();
11046 write_upload_pack_packfile_response(&mut encoded, &response)
11047 .expect("test operation should succeed");
11048 encoded.extend_from_slice(b"tail");
11049
11050 let mut input = encoded.as_slice();
11051 assert_eq!(
11052 read_upload_pack_packfile_response(ObjectFormat::Sha1, &mut input)
11053 .expect("test operation should succeed"),
11054 response
11055 );
11056 assert_eq!(input, b"tail");
11057 }
11058
11059 #[test]
11060 fn upload_pack_packfile_response_rejects_malformed_streams() {
11061 assert!(
11062 parse_upload_pack_packfile_response(
11063 ObjectFormat::Sha1,
11064 &[PktLineFrame::Data(b"NAK\n".to_vec())],
11065 )
11066 .is_err()
11067 );
11068 assert!(
11069 parse_upload_pack_packfile_response(
11070 ObjectFormat::Sha1,
11071 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
11072 )
11073 .is_err()
11074 );
11075 assert!(
11076 parse_upload_pack_packfile_response(
11077 ObjectFormat::Sha1,
11078 &[
11079 PktLineFrame::Data(b"\x01PACK".to_vec()),
11080 PktLineFrame::Data(
11081 b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()
11082 ),
11083 PktLineFrame::Flush,
11084 ],
11085 )
11086 .is_err()
11087 );
11088 assert!(
11089 parse_upload_pack_packfile_response(
11090 ObjectFormat::Sha1,
11091 &[
11092 PktLineFrame::Data(b"NAK\n".to_vec()),
11093 PktLineFrame::Flush,
11094 PktLineFrame::Data(b"\x01PACK".to_vec()),
11095 ],
11096 )
11097 .is_err()
11098 );
11099 assert!(
11100 parse_upload_pack_packfile_response(
11101 ObjectFormat::Sha1,
11102 &[
11103 PktLineFrame::Data(b"NAK\n".to_vec()),
11104 PktLineFrame::Data(b"\x04bad".to_vec()),
11105 PktLineFrame::Flush,
11106 ],
11107 )
11108 .is_err()
11109 );
11110 }
11111
11112 #[test]
11113 fn upload_pack_raw_packfile_response_parses_acknowledgments_and_raw_pack() {
11114 let oid = ObjectId::from_hex(
11115 ObjectFormat::Sha1,
11116 "1111111111111111111111111111111111111111",
11117 )
11118 .expect("test operation should succeed");
11119 let response = UploadPackRawPackfileResponse {
11120 acknowledgments: vec![
11121 UploadPackAcknowledgment::Ack {
11122 oid,
11123 status: Some(UploadPackAckStatus::Common),
11124 },
11125 UploadPackAcknowledgment::Nak,
11126 ],
11127 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11128 };
11129 let encoded = encode_upload_pack_raw_packfile_response(&response)
11130 .expect("test operation should succeed");
11131 assert_eq!(
11132 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &encoded)
11133 .expect("test operation should succeed"),
11134 response
11135 );
11136 }
11137
11138 #[test]
11139 fn upload_pack_raw_packfile_response_streams_round_trip() {
11140 let response = UploadPackRawPackfileResponse {
11141 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11142 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11143 };
11144 let mut encoded = Vec::new();
11145 write_upload_pack_raw_packfile_response(&mut encoded, &response)
11146 .expect("test operation should succeed");
11147 assert_eq!(
11148 encoded,
11149 encode_upload_pack_raw_packfile_response(&response)
11150 .expect("test operation should succeed")
11151 );
11152
11153 let mut input = encoded.as_slice();
11154 assert_eq!(
11155 read_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &mut input)
11156 .expect("test operation should succeed"),
11157 response
11158 );
11159 assert!(input.is_empty());
11160 }
11161
11162 #[test]
11163 fn upload_pack_raw_packfile_response_rejects_malformed_streams() {
11164 let ack = PktLineFrame::data(b"NAK\n".to_vec())
11165 .expect("test operation should succeed")
11166 .try_encode()
11167 .expect("test operation should succeed");
11168 let bad_ack = PktLineFrame::data(b"ACK not-an-oid\n".to_vec())
11169 .expect("test operation should succeed")
11170 .try_encode()
11171 .expect("test operation should succeed");
11172 let non_ack =
11173 PktLineFrame::data(b"have 1111111111111111111111111111111111111111\n".to_vec())
11174 .expect("test operation should succeed")
11175 .try_encode()
11176 .expect("test operation should succeed");
11177 let mut garbage_after_ack = ack.clone();
11178 garbage_after_ack.extend_from_slice(b"garbage");
11179
11180 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"").is_err());
11181 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &ack).is_err());
11182 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &bad_ack).is_err());
11183 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"0000PACK").is_err());
11184 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &non_ack).is_err());
11185 assert!(
11186 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &garbage_after_ack)
11187 .is_err()
11188 );
11189 assert!(
11190 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11191 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11192 packfile: Vec::new(),
11193 })
11194 .is_err()
11195 );
11196 assert!(
11197 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11198 acknowledgments: Vec::new(),
11199 packfile: b"not-a-pack".to_vec(),
11200 })
11201 .is_err()
11202 );
11203 }
11204
11205 #[test]
11206 fn upload_pack_request_encodes_deepen_request() {
11207 let want = ObjectId::from_hex(
11212 ObjectFormat::Sha1,
11213 "1111111111111111111111111111111111111111",
11214 )
11215 .expect("test operation should succeed");
11216 let boundary = ObjectId::from_hex(
11217 ObjectFormat::Sha1,
11218 "2222222222222222222222222222222222222222",
11219 )
11220 .expect("test operation should succeed");
11221 let request = UploadPackRequest {
11222 wants: vec![want],
11223 capabilities: vec![Capability {
11224 name: "shallow".into(),
11225 value: None,
11226 }],
11227 shallow: vec![boundary],
11228 deepen: Some(1),
11229 ..UploadPackRequest::default()
11230 };
11231 let mut encoded = Vec::new();
11232 write_upload_pack_request(&mut encoded, Some(&request))
11233 .expect("test operation should succeed");
11234 let mut expected = Vec::new();
11235 expected.extend_from_slice(b"003awant 1111111111111111111111111111111111111111 shallow\n");
11236 expected.extend_from_slice(b"0035shallow 2222222222222222222222222222222222222222\n");
11237 expected.extend_from_slice(b"000ddeepen 1\n");
11238 expected.extend_from_slice(b"0000");
11239 assert_eq!(encoded, expected);
11240 }
11241
11242 #[test]
11243 fn upload_pack_shallow_info_response_parses_shallow_unshallow_and_pack() {
11244 let shallow = ObjectId::from_hex(
11248 ObjectFormat::Sha1,
11249 "1111111111111111111111111111111111111111",
11250 )
11251 .expect("test operation should succeed");
11252 let unshallow = ObjectId::from_hex(
11253 ObjectFormat::Sha1,
11254 "2222222222222222222222222222222222222222",
11255 )
11256 .expect("test operation should succeed");
11257 let mut input = Vec::new();
11258 input.extend_from_slice(b"0035shallow 1111111111111111111111111111111111111111\n");
11259 input.extend_from_slice(b"0037unshallow 2222222222222222222222222222222222222222\n");
11260 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11262 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11263
11264 let (entries, response) =
11265 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11266 .expect("test operation should succeed");
11267 assert_eq!(
11268 entries,
11269 vec![
11270 ProtocolV2FetchShallowInfo::Shallow(shallow),
11271 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
11272 ]
11273 );
11274 assert_eq!(
11275 response,
11276 UploadPackRawPackfileResponse {
11277 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11278 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11279 }
11280 );
11281
11282 let mut stream = input.as_slice();
11284 let (read_entries, read_response) =
11285 read_upload_pack_shallow_info_and_raw_packfile_response(
11286 ObjectFormat::Sha1,
11287 &mut stream,
11288 )
11289 .expect("test operation should succeed");
11290 assert_eq!(read_entries, entries);
11291 assert_eq!(read_response, response);
11292 }
11293
11294 #[test]
11295 fn upload_pack_shallow_info_response_handles_empty_shallow_section() {
11296 let mut input = Vec::new();
11299 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11301 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11302
11303 let (entries, response) =
11304 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11305 .expect("test operation should succeed");
11306 assert!(entries.is_empty());
11307 assert_eq!(
11308 response.acknowledgments,
11309 vec![UploadPackAcknowledgment::Nak]
11310 );
11311 assert!(response.packfile.starts_with(b"PACK"));
11312 }
11313
11314 #[test]
11315 fn upload_pack_shallow_info_response_rejects_malformed_sections() {
11316 let truncated = b"0035shallow 1111111111111111111111111111111111111111\n".to_vec();
11318 assert!(
11319 parse_upload_pack_shallow_info_and_raw_packfile_response(
11320 ObjectFormat::Sha1,
11321 &truncated
11322 )
11323 .is_err()
11324 );
11325 let mut delimiter_section = Vec::new();
11327 delimiter_section.extend_from_slice(b"0001"); assert!(
11329 parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &delimiter_section).is_err()
11330 );
11331 let mut bad_line = Vec::new();
11333 bad_line.extend_from_slice(b"0008NAK\n");
11334 assert!(parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &bad_line).is_err());
11335 let mut no_pack = Vec::new();
11337 no_pack.extend_from_slice(b"0000"); no_pack.extend_from_slice(b"0008NAK\n");
11339 assert!(
11340 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &no_pack)
11341 .is_err()
11342 );
11343 }
11344
11345 #[test]
11346 fn receive_pack_request_parses_and_encodes_commands() {
11347 let old_id = ObjectId::from_hex(
11348 ObjectFormat::Sha1,
11349 "1111111111111111111111111111111111111111",
11350 )
11351 .expect("test operation should succeed");
11352 let new_id = ObjectId::from_hex(
11353 ObjectFormat::Sha1,
11354 "2222222222222222222222222222222222222222",
11355 )
11356 .expect("test operation should succeed");
11357 let delete_old_id = ObjectId::from_hex(
11358 ObjectFormat::Sha1,
11359 "3333333333333333333333333333333333333333",
11360 )
11361 .expect("test operation should succeed");
11362 let zero = ObjectId::from_hex(
11363 ObjectFormat::Sha1,
11364 "0000000000000000000000000000000000000000",
11365 )
11366 .expect("test operation should succeed");
11367 let shallow = ObjectId::from_hex(
11368 ObjectFormat::Sha1,
11369 "4444444444444444444444444444444444444444",
11370 )
11371 .expect("test operation should succeed");
11372 let frames = vec![
11373 PktLineFrame::Data(b"shallow 4444444444444444444444444444444444444444\n".to_vec()),
11374 PktLineFrame::Data(
11375 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status side-band-64k agent=git/2.54.0\n"
11376 .to_vec(),
11377 ),
11378 PktLineFrame::Data(
11379 b"3333333333333333333333333333333333333333 0000000000000000000000000000000000000000 refs/heads/old\n"
11380 .to_vec(),
11381 ),
11382 PktLineFrame::Flush,
11383 ];
11384 let request = parse_receive_pack_request(ObjectFormat::Sha1, &frames)
11385 .expect("test operation should succeed");
11386 assert_eq!(
11387 request,
11388 ReceivePackRequest {
11389 shallow: vec![shallow],
11390 commands: vec![
11391 ReceivePackCommand {
11392 old_id,
11393 new_id,
11394 name: "refs/heads/main".into(),
11395 },
11396 ReceivePackCommand {
11397 old_id: delete_old_id,
11398 new_id: zero,
11399 name: "refs/heads/old".into(),
11400 },
11401 ],
11402 capabilities: vec![
11403 Capability {
11404 name: "report-status".into(),
11405 value: None,
11406 },
11407 Capability {
11408 name: "side-band-64k".into(),
11409 value: None,
11410 },
11411 Capability {
11412 name: "agent".into(),
11413 value: Some("git/2.54.0".into()),
11414 },
11415 ],
11416 }
11417 );
11418 assert_eq!(
11419 encode_receive_pack_request(&request).expect("test operation should succeed"),
11420 frames
11421 );
11422 assert_eq!(
11423 parse_receive_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
11424 .expect("test operation should succeed"),
11425 ReceivePackRequest::default()
11426 );
11427 }
11428
11429 #[test]
11430 fn receive_pack_request_streams_round_trip() {
11431 let request = ReceivePackRequest {
11432 commands: vec![ReceivePackCommand {
11433 old_id: ObjectId::from_hex(
11434 ObjectFormat::Sha1,
11435 "0000000000000000000000000000000000000000",
11436 )
11437 .expect("test operation should succeed"),
11438 new_id: ObjectId::from_hex(
11439 ObjectFormat::Sha1,
11440 "1111111111111111111111111111111111111111",
11441 )
11442 .expect("test operation should succeed"),
11443 name: "refs/heads/main".into(),
11444 }],
11445 capabilities: vec![Capability {
11446 name: "report-status".into(),
11447 value: None,
11448 }],
11449 ..ReceivePackRequest::default()
11450 };
11451 let mut encoded = Vec::new();
11452 write_receive_pack_request(&mut encoded, &request).expect("test operation should succeed");
11453 encoded.extend_from_slice(b"PACK");
11454
11455 let mut input = encoded.as_slice();
11456 assert_eq!(
11457 read_receive_pack_request(ObjectFormat::Sha1, &mut input)
11458 .expect("test operation should succeed"),
11459 request
11460 );
11461 assert_eq!(input, b"PACK");
11462 }
11463
11464 #[test]
11465 fn receive_pack_request_rejects_malformed_commands() {
11466 assert!(
11467 parse_receive_pack_request(
11468 ObjectFormat::Sha1,
11469 &[PktLineFrame::Data(
11470 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11471 .to_vec(),
11472 )],
11473 )
11474 .is_err()
11475 );
11476 assert!(
11477 parse_receive_pack_request(
11478 ObjectFormat::Sha1,
11479 &[
11480 PktLineFrame::Data(
11481 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11482 .to_vec(),
11483 ),
11484 PktLineFrame::Data(
11485 b"shallow 3333333333333333333333333333333333333333\n".to_vec(),
11486 ),
11487 PktLineFrame::Flush,
11488 ],
11489 )
11490 .is_err()
11491 );
11492 assert!(
11493 parse_receive_pack_request(
11494 ObjectFormat::Sha1,
11495 &[
11496 PktLineFrame::Data(
11497 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status\n"
11498 .to_vec(),
11499 ),
11500 PktLineFrame::Data(
11501 b"3333333333333333333333333333333333333333 4444444444444444444444444444444444444444 refs/heads/next\0side-band-64k\n"
11502 .to_vec(),
11503 ),
11504 PktLineFrame::Flush,
11505 ],
11506 )
11507 .is_err()
11508 );
11509 assert!(
11510 parse_receive_pack_request(
11511 ObjectFormat::Sha1,
11512 &[
11513 PktLineFrame::Data(
11514 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
11515 ),
11516 PktLineFrame::Flush,
11517 ],
11518 )
11519 .is_err()
11520 );
11521 assert!(
11522 encode_receive_pack_request(&ReceivePackRequest {
11523 shallow: vec![
11524 ObjectId::from_hex(
11525 ObjectFormat::Sha1,
11526 "1111111111111111111111111111111111111111",
11527 )
11528 .expect("test operation should succeed")
11529 ],
11530 ..ReceivePackRequest::default()
11531 })
11532 .is_err()
11533 );
11534 assert!(
11535 encode_receive_pack_request(&ReceivePackRequest {
11536 commands: vec![ReceivePackCommand {
11537 old_id: ObjectId::from_hex(
11538 ObjectFormat::Sha1,
11539 "1111111111111111111111111111111111111111",
11540 )
11541 .expect("test operation should succeed"),
11542 new_id: ObjectId::from_hex(
11543 ObjectFormat::Sha1,
11544 "2222222222222222222222222222222222222222",
11545 )
11546 .expect("test operation should succeed"),
11547 name: "bad ref".into(),
11548 }],
11549 ..ReceivePackRequest::default()
11550 })
11551 .is_err()
11552 );
11553 }
11554
11555 #[test]
11556 fn receive_pack_features_parse_encode_and_validate_push_request() {
11557 let capabilities = vec![
11558 Capability {
11559 name: "report-status".into(),
11560 value: None,
11561 },
11562 Capability {
11563 name: "report-status-v2".into(),
11564 value: None,
11565 },
11566 Capability {
11567 name: "delete-refs".into(),
11568 value: None,
11569 },
11570 Capability {
11571 name: "ofs-delta".into(),
11572 value: None,
11573 },
11574 Capability {
11575 name: "atomic".into(),
11576 value: None,
11577 },
11578 Capability {
11579 name: "push-options".into(),
11580 value: None,
11581 },
11582 Capability {
11583 name: "side-band-64k".into(),
11584 value: None,
11585 },
11586 Capability {
11587 name: "quiet".into(),
11588 value: None,
11589 },
11590 Capability {
11591 name: "no-thin".into(),
11592 value: None,
11593 },
11594 Capability {
11595 name: "agent".into(),
11596 value: Some("git/2.54.0".into()),
11597 },
11598 Capability {
11599 name: "object-format".into(),
11600 value: Some("sha256".into()),
11601 },
11602 Capability {
11603 name: "custom".into(),
11604 value: Some("value".into()),
11605 },
11606 ];
11607 let features =
11608 parse_receive_pack_features(&capabilities).expect("test operation should succeed");
11609 assert_eq!(
11610 features,
11611 ReceivePackFeatures {
11612 report_status: true,
11613 report_status_v2: true,
11614 delete_refs: true,
11615 ofs_delta: true,
11616 atomic: true,
11617 push_options: true,
11618 side_band_64k: true,
11619 quiet: true,
11620 no_thin: true,
11621 agent: Some("git/2.54.0".into()),
11622 object_format: Some(ObjectFormat::Sha256),
11623 unknown: vec![Capability {
11624 name: "custom".into(),
11625 value: Some("value".into()),
11626 }],
11627 }
11628 );
11629 assert_eq!(
11630 encode_receive_pack_features(&features).expect("test operation should succeed"),
11631 capabilities
11632 );
11633
11634 let request = ReceivePackPushRequest {
11635 commands: ReceivePackRequest {
11636 commands: vec![ReceivePackCommand {
11637 old_id: ObjectId::from_hex(
11638 ObjectFormat::Sha1,
11639 "1111111111111111111111111111111111111111",
11640 )
11641 .expect("test operation should succeed"),
11642 new_id: ObjectId::from_hex(
11643 ObjectFormat::Sha1,
11644 "2222222222222222222222222222222222222222",
11645 )
11646 .expect("test operation should succeed"),
11647 name: "refs/heads/main".into(),
11648 }],
11649 capabilities: vec![
11650 Capability {
11651 name: "report-status".into(),
11652 value: None,
11653 },
11654 Capability {
11655 name: "ofs-delta".into(),
11656 value: None,
11657 },
11658 Capability {
11659 name: "push-options".into(),
11660 value: None,
11661 },
11662 Capability {
11663 name: "side-band-64k".into(),
11664 value: None,
11665 },
11666 Capability {
11667 name: "agent".into(),
11668 value: Some("sley".into()),
11669 },
11670 ],
11671 ..ReceivePackRequest::default()
11672 },
11673 push_options: Some(vec!["ci.skip".into()]),
11674 packfile: b"PACKpayload".to_vec(),
11675 };
11676 validate_receive_pack_push_request_features(&features, &request)
11677 .expect("test operation should succeed");
11678 }
11679
11680 #[test]
11681 fn receive_pack_features_reject_invalid_push_requests() {
11682 let old_id = ObjectId::from_hex(
11683 ObjectFormat::Sha1,
11684 "1111111111111111111111111111111111111111",
11685 )
11686 .expect("test operation should succeed");
11687 let new_id = ObjectId::from_hex(
11688 ObjectFormat::Sha1,
11689 "2222222222222222222222222222222222222222",
11690 )
11691 .expect("test operation should succeed");
11692 let zero = ObjectId::from_hex(
11693 ObjectFormat::Sha1,
11694 "0000000000000000000000000000000000000000",
11695 )
11696 .expect("test operation should succeed");
11697 let features = ReceivePackFeatures {
11698 report_status: true,
11699 push_options: true,
11700 ..ReceivePackFeatures::default()
11701 };
11702 let update = ReceivePackCommand {
11703 old_id: old_id.clone(),
11704 new_id: new_id.clone(),
11705 name: "refs/heads/main".into(),
11706 };
11707
11708 assert!(
11709 validate_receive_pack_push_request_features(
11710 &features,
11711 &ReceivePackPushRequest {
11712 commands: ReceivePackRequest {
11713 commands: vec![update.clone()],
11714 capabilities: vec![Capability {
11715 name: "push-options".into(),
11716 value: None,
11717 }],
11718 ..ReceivePackRequest::default()
11719 },
11720 push_options: None,
11721 packfile: b"PACKpayload".to_vec(),
11722 },
11723 )
11724 .is_err()
11725 );
11726 assert!(
11727 validate_receive_pack_push_request_features(
11728 &features,
11729 &ReceivePackPushRequest {
11730 commands: ReceivePackRequest {
11731 commands: vec![update.clone()],
11732 ..ReceivePackRequest::default()
11733 },
11734 push_options: Some(Vec::new()),
11735 packfile: b"PACKpayload".to_vec(),
11736 },
11737 )
11738 .is_err()
11739 );
11740 assert!(
11741 validate_receive_pack_push_request_features(
11742 &features,
11743 &ReceivePackPushRequest {
11744 commands: ReceivePackRequest {
11745 commands: vec![ReceivePackCommand {
11746 old_id: old_id.clone(),
11747 new_id: zero.clone(),
11748 name: "refs/heads/main".into(),
11749 }],
11750 ..ReceivePackRequest::default()
11751 },
11752 push_options: None,
11753 packfile: Vec::new(),
11754 },
11755 )
11756 .is_err()
11757 );
11758 assert!(
11759 validate_receive_pack_push_request_features(
11760 &features,
11761 &ReceivePackPushRequest {
11762 commands: ReceivePackRequest {
11763 commands: vec![update.clone()],
11764 ..ReceivePackRequest::default()
11765 },
11766 push_options: None,
11767 packfile: Vec::new(),
11768 },
11769 )
11770 .is_err()
11771 );
11772 assert!(
11773 validate_receive_pack_push_request_features(
11774 &ReceivePackFeatures {
11775 delete_refs: true,
11776 ..ReceivePackFeatures::default()
11777 },
11778 &ReceivePackPushRequest {
11779 commands: ReceivePackRequest {
11780 commands: vec![ReceivePackCommand {
11781 old_id,
11782 new_id: zero,
11783 name: "refs/heads/main".into(),
11784 }],
11785 ..ReceivePackRequest::default()
11786 },
11787 push_options: None,
11788 packfile: b"PACKpayload".to_vec(),
11789 },
11790 )
11791 .is_err()
11792 );
11793 assert!(
11794 validate_receive_pack_push_request_features(
11795 &features,
11796 &ReceivePackPushRequest {
11797 commands: ReceivePackRequest {
11798 commands: vec![update],
11799 capabilities: vec![Capability {
11800 name: "atomic".into(),
11801 value: None,
11802 }],
11803 ..ReceivePackRequest::default()
11804 },
11805 push_options: None,
11806 packfile: b"PACKpayload".to_vec(),
11807 },
11808 )
11809 .is_err()
11810 );
11811
11812 assert!(
11813 parse_receive_pack_features(&[
11814 Capability {
11815 name: "push-options".into(),
11816 value: None,
11817 },
11818 Capability {
11819 name: "push-options".into(),
11820 value: None,
11821 },
11822 ])
11823 .is_err()
11824 );
11825 assert!(
11826 encode_receive_pack_features(&ReceivePackFeatures {
11827 unknown: vec![Capability {
11828 name: "atomic".into(),
11829 value: None,
11830 }],
11831 ..ReceivePackFeatures::default()
11832 })
11833 .is_err()
11834 );
11835 }
11836
11837 #[test]
11838 fn receive_pack_apply_helper_installs_pack_verifies_objects_and_reports_ok() {
11839 let old_id = ObjectId::from_hex(
11840 ObjectFormat::Sha1,
11841 "1111111111111111111111111111111111111111",
11842 )
11843 .expect("test operation should succeed");
11844 let new_id = ObjectId::from_hex(
11845 ObjectFormat::Sha1,
11846 "2222222222222222222222222222222222222222",
11847 )
11848 .expect("test operation should succeed");
11849 let request = ReceivePackPushRequest {
11850 commands: ReceivePackRequest {
11851 commands: vec![ReceivePackCommand {
11852 old_id: old_id.clone(),
11853 new_id: new_id.clone(),
11854 name: "refs/heads/main".into(),
11855 }],
11856 ..ReceivePackRequest::default()
11857 },
11858 packfile: b"PACKpayload".to_vec(),
11859 ..ReceivePackPushRequest::default()
11860 };
11861 let installed = std::cell::Cell::new(false);
11862 let applied = std::cell::RefCell::new(Vec::new());
11863
11864 let report = apply_receive_pack_push_request(
11865 &ReceivePackFeatures::default(),
11866 &request,
11867 |_| unreachable!("update stale-old checks belong to the ref transaction callback"),
11868 |packfile| {
11869 assert_eq!(packfile, b"PACKpayload");
11870 installed.set(true);
11871 Ok(())
11872 },
11873 |oid| Ok(oid == &new_id),
11874 |commands| {
11875 applied.borrow_mut().extend_from_slice(commands);
11876 Ok(())
11877 },
11878 |_| unreachable!("no delete command should be applied"),
11879 )
11880 .expect("test operation should succeed");
11881
11882 assert!(installed.get());
11883 assert_eq!(applied.into_inner(), request.commands.commands);
11884 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11885 assert_eq!(
11886 report.commands,
11887 vec![ReceivePackCommandStatus::Ok {
11888 name: "refs/heads/main".into(),
11889 }]
11890 );
11891 }
11892
11893 #[test]
11894 fn receive_pack_apply_helper_preserves_delete_only_and_stale_delete_rules() {
11895 let old_id = ObjectId::from_hex(
11896 ObjectFormat::Sha1,
11897 "1111111111111111111111111111111111111111",
11898 )
11899 .expect("test operation should succeed");
11900 let other_id = ObjectId::from_hex(
11901 ObjectFormat::Sha1,
11902 "2222222222222222222222222222222222222222",
11903 )
11904 .expect("test operation should succeed");
11905 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
11906 let request = ReceivePackPushRequest {
11907 commands: ReceivePackRequest {
11908 commands: vec![ReceivePackCommand {
11909 old_id: old_id.clone(),
11910 new_id: zero,
11911 name: "refs/heads/main".into(),
11912 }],
11913 ..ReceivePackRequest::default()
11914 },
11915 ..ReceivePackPushRequest::default()
11916 };
11917 let features = ReceivePackFeatures {
11918 delete_refs: true,
11919 ..ReceivePackFeatures::default()
11920 };
11921 let installed = std::cell::Cell::new(false);
11922 let deleted = std::cell::RefCell::new(Vec::new());
11923
11924 let report = apply_receive_pack_push_request(
11925 &features,
11926 &request,
11927 |_| Ok(Some(old_id.clone())),
11928 |_| {
11929 installed.set(true);
11930 Ok(())
11931 },
11932 |_| Ok(false),
11933 |_| unreachable!("delete-only request should not apply updates"),
11934 |command| {
11935 deleted.borrow_mut().push(command.name.clone());
11936 Ok(())
11937 },
11938 )
11939 .expect("test operation should succeed");
11940
11941 assert!(!installed.get());
11942 assert_eq!(deleted.into_inner(), vec!["refs/heads/main"]);
11943 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11944 assert!(
11945 apply_receive_pack_push_request(
11946 &features,
11947 &request,
11948 |_| Ok(Some(other_id.clone())),
11949 |_| Ok(()),
11950 |_| Ok(false),
11951 |_| Ok(()),
11952 |_| Ok(()),
11953 )
11954 .is_err()
11955 );
11956 }
11957
11958 #[test]
11959 fn receive_pack_push_request_parses_commands_options_and_packfile() {
11960 let command = ReceivePackCommand {
11961 old_id: ObjectId::from_hex(
11962 ObjectFormat::Sha1,
11963 "1111111111111111111111111111111111111111",
11964 )
11965 .expect("test operation should succeed"),
11966 new_id: ObjectId::from_hex(
11967 ObjectFormat::Sha1,
11968 "2222222222222222222222222222222222222222",
11969 )
11970 .expect("test operation should succeed"),
11971 name: "refs/heads/main".into(),
11972 };
11973 let expected = ReceivePackPushRequest {
11974 commands: ReceivePackRequest {
11975 commands: vec![command],
11976 capabilities: vec![
11977 Capability {
11978 name: "report-status".into(),
11979 value: None,
11980 },
11981 Capability {
11982 name: "push-options".into(),
11983 value: None,
11984 },
11985 ],
11986 ..ReceivePackRequest::default()
11987 },
11988 push_options: Some(vec!["ci.skip".into(), "deploy=staging".into()]),
11989 packfile: b"PACK\x00\x00\x00\x02payload".to_vec(),
11990 };
11991 let encoded =
11992 encode_receive_pack_push_request(&expected).expect("test operation should succeed");
11993
11994 assert_eq!(
11995 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true)
11996 .expect("test operation should succeed"),
11997 expected
11998 );
11999 }
12000
12001 #[test]
12002 fn receive_pack_push_request_preserves_packfile_without_push_options() {
12003 let request = ReceivePackPushRequest {
12004 commands: ReceivePackRequest {
12005 commands: vec![ReceivePackCommand {
12006 old_id: ObjectId::from_hex(
12007 ObjectFormat::Sha1,
12008 "1111111111111111111111111111111111111111",
12009 )
12010 .expect("test operation should succeed"),
12011 new_id: ObjectId::from_hex(
12012 ObjectFormat::Sha1,
12013 "2222222222222222222222222222222222222222",
12014 )
12015 .expect("test operation should succeed"),
12016 name: "refs/heads/main".into(),
12017 }],
12018 ..ReceivePackRequest::default()
12019 },
12020 push_options: None,
12021 packfile: b"0000PACK-like bytes stay raw".to_vec(),
12022 };
12023 let encoded =
12024 encode_receive_pack_push_request(&request).expect("test operation should succeed");
12025
12026 assert_eq!(
12027 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, false)
12028 .expect("test operation should succeed"),
12029 request
12030 );
12031 }
12032
12033 #[test]
12034 fn receive_pack_push_request_streams_round_trip() {
12035 let request = ReceivePackPushRequest {
12036 commands: ReceivePackRequest {
12037 commands: vec![ReceivePackCommand {
12038 old_id: ObjectId::from_hex(
12039 ObjectFormat::Sha1,
12040 "1111111111111111111111111111111111111111",
12041 )
12042 .expect("test operation should succeed"),
12043 new_id: ObjectId::from_hex(
12044 ObjectFormat::Sha1,
12045 "2222222222222222222222222222222222222222",
12046 )
12047 .expect("test operation should succeed"),
12048 name: "refs/heads/main".into(),
12049 }],
12050 capabilities: vec![Capability {
12051 name: "push-options".into(),
12052 value: None,
12053 }],
12054 ..ReceivePackRequest::default()
12055 },
12056 push_options: Some(Vec::new()),
12057 packfile: b"PACKpayload".to_vec(),
12058 };
12059 let mut encoded = Vec::new();
12060 write_receive_pack_push_request(&mut encoded, &request)
12061 .expect("test operation should succeed");
12062
12063 assert_eq!(
12064 read_receive_pack_push_request(ObjectFormat::Sha1, &mut encoded.as_slice(), true)
12065 .expect("test operation should succeed"),
12066 request
12067 );
12068 }
12069
12070 #[test]
12071 fn receive_pack_push_request_rejects_malformed_sections() {
12072 assert!(
12073 parse_receive_pack_push_request(
12074 ObjectFormat::Sha1,
12075 b"0014not-a-command\n0000PACK",
12076 false,
12077 )
12078 .is_err()
12079 );
12080
12081 let request = ReceivePackPushRequest {
12082 commands: ReceivePackRequest {
12083 commands: vec![ReceivePackCommand {
12084 old_id: ObjectId::from_hex(
12085 ObjectFormat::Sha1,
12086 "1111111111111111111111111111111111111111",
12087 )
12088 .expect("test operation should succeed"),
12089 new_id: ObjectId::from_hex(
12090 ObjectFormat::Sha1,
12091 "2222222222222222222222222222222222222222",
12092 )
12093 .expect("test operation should succeed"),
12094 name: "refs/heads/main".into(),
12095 }],
12096 ..ReceivePackRequest::default()
12097 },
12098 push_options: None,
12099 packfile: b"PACKpayload".to_vec(),
12100 };
12101 let encoded =
12102 encode_receive_pack_push_request(&request).expect("test operation should succeed");
12103 assert!(parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true).is_err());
12104
12105 assert!(
12106 encode_receive_pack_push_request(&ReceivePackPushRequest {
12107 commands: ReceivePackRequest {
12108 shallow: vec![
12109 ObjectId::from_hex(
12110 ObjectFormat::Sha1,
12111 "1111111111111111111111111111111111111111",
12112 )
12113 .expect("test operation should succeed")
12114 ],
12115 ..ReceivePackRequest::default()
12116 },
12117 push_options: None,
12118 packfile: Vec::new(),
12119 })
12120 .is_err()
12121 );
12122 }
12123
12124 #[test]
12125 fn receive_pack_report_status_parses_and_encodes_status_lines() {
12126 let frames = vec![
12127 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12128 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12129 PktLineFrame::Data(b"ng refs/heads/old non-fast-forward\n".to_vec()),
12130 PktLineFrame::Flush,
12131 ];
12132 let report =
12133 parse_receive_pack_report_status(&frames).expect("test operation should succeed");
12134 assert_eq!(
12135 report,
12136 ReceivePackReportStatus {
12137 unpack: ReceivePackUnpackStatus::Ok,
12138 commands: vec![
12139 ReceivePackCommandStatus::Ok {
12140 name: "refs/heads/main".into(),
12141 },
12142 ReceivePackCommandStatus::Ng {
12143 name: "refs/heads/old".into(),
12144 message: "non-fast-forward".into(),
12145 },
12146 ],
12147 }
12148 );
12149 assert_eq!(
12150 encode_receive_pack_report_status(&report).expect("test operation should succeed"),
12151 frames
12152 );
12153
12154 let frames = vec![
12155 PktLineFrame::Data(b"unpack pack exceeds maximum size\n".to_vec()),
12156 PktLineFrame::Flush,
12157 ];
12158 assert_eq!(
12159 parse_receive_pack_report_status(&frames).expect("test operation should succeed"),
12160 ReceivePackReportStatus {
12161 unpack: ReceivePackUnpackStatus::Error("pack exceeds maximum size".into()),
12162 commands: Vec::new(),
12163 }
12164 );
12165 }
12166
12167 #[test]
12168 fn receive_pack_report_status_streams_round_trip() {
12169 let report = ReceivePackReportStatus {
12170 unpack: ReceivePackUnpackStatus::Ok,
12171 commands: vec![ReceivePackCommandStatus::Ok {
12172 name: "refs/heads/main".into(),
12173 }],
12174 };
12175 let mut encoded = Vec::new();
12176 write_receive_pack_report_status(&mut encoded, &report)
12177 .expect("test operation should succeed");
12178 encoded.extend_from_slice(b"tail");
12179
12180 let mut input = encoded.as_slice();
12181 assert_eq!(
12182 read_receive_pack_report_status(&mut input).expect("test operation should succeed"),
12183 report
12184 );
12185 assert_eq!(input, b"tail");
12186 }
12187
12188 #[test]
12189 fn receive_pack_report_status_rejects_malformed_status_lines() {
12190 assert!(parse_receive_pack_report_status(&[]).is_err());
12191 assert!(
12192 parse_receive_pack_report_status(&[
12193 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12194 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12195 ])
12196 .is_err()
12197 );
12198 assert!(
12199 parse_receive_pack_report_status(&[
12200 PktLineFrame::Flush,
12201 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12202 ])
12203 .is_err()
12204 );
12205 assert!(
12206 parse_receive_pack_report_status(&[
12207 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12208 PktLineFrame::Data(b"bad refs/heads/main\n".to_vec()),
12209 PktLineFrame::Flush,
12210 ])
12211 .is_err()
12212 );
12213 assert!(
12214 parse_receive_pack_report_status(&[
12215 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12216 PktLineFrame::Data(b"ng refs/heads/main\n".to_vec()),
12217 PktLineFrame::Flush,
12218 ])
12219 .is_err()
12220 );
12221 assert!(
12222 encode_receive_pack_report_status(&ReceivePackReportStatus {
12223 unpack: ReceivePackUnpackStatus::Error("".into()),
12224 commands: Vec::new(),
12225 })
12226 .is_err()
12227 );
12228 assert!(
12229 encode_receive_pack_report_status(&ReceivePackReportStatus {
12230 unpack: ReceivePackUnpackStatus::Ok,
12231 commands: vec![ReceivePackCommandStatus::Ok {
12232 name: "bad ref".into(),
12233 }],
12234 })
12235 .is_err()
12236 );
12237 }
12238
12239 #[test]
12240 fn receive_pack_report_status_v2_parses_and_encodes_options() {
12241 let old_oid = ObjectId::from_hex(
12242 ObjectFormat::Sha1,
12243 "1111111111111111111111111111111111111111",
12244 )
12245 .expect("test operation should succeed");
12246 let new_oid = ObjectId::from_hex(
12247 ObjectFormat::Sha1,
12248 "2222222222222222222222222222222222222222",
12249 )
12250 .expect("test operation should succeed");
12251 let frames = vec![
12252 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12253 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12254 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12255 PktLineFrame::Data(
12256 b"option old-oid 1111111111111111111111111111111111111111\n".to_vec(),
12257 ),
12258 PktLineFrame::Data(
12259 b"option new-oid 2222222222222222222222222222222222222222\n".to_vec(),
12260 ),
12261 PktLineFrame::Data(b"option forced-update\n".to_vec()),
12262 PktLineFrame::Data(b"ng refs/heads/old rejected by hook\n".to_vec()),
12263 PktLineFrame::Flush,
12264 ];
12265 let report = parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &frames)
12266 .expect("test operation should succeed");
12267 assert_eq!(
12268 report,
12269 ReceivePackReportStatusV2 {
12270 unpack: ReceivePackUnpackStatus::Ok,
12271 commands: vec![
12272 ReceivePackCommandStatusV2::Ok {
12273 name: "refs/for/main".into(),
12274 options: ReceivePackCommandStatusV2Options {
12275 refname: Some("refs/heads/main".into()),
12276 old_oid: Some(old_oid),
12277 new_oid: Some(new_oid),
12278 forced_update: true,
12279 },
12280 },
12281 ReceivePackCommandStatusV2::Ng {
12282 name: "refs/heads/old".into(),
12283 message: "rejected by hook".into(),
12284 },
12285 ],
12286 }
12287 );
12288 assert_eq!(
12289 encode_receive_pack_report_status_v2(&report).expect("test operation should succeed"),
12290 frames
12291 );
12292 }
12293
12294 #[test]
12295 fn receive_pack_report_status_v2_streams_round_trip() {
12296 let report = ReceivePackReportStatusV2 {
12297 unpack: ReceivePackUnpackStatus::Ok,
12298 commands: vec![ReceivePackCommandStatusV2::Ok {
12299 name: "refs/for/main".into(),
12300 options: ReceivePackCommandStatusV2Options {
12301 refname: Some("refs/heads/main".into()),
12302 old_oid: None,
12303 new_oid: None,
12304 forced_update: false,
12305 },
12306 }],
12307 };
12308 let mut encoded = Vec::new();
12309 write_receive_pack_report_status_v2(&mut encoded, &report)
12310 .expect("test operation should succeed");
12311 encoded.extend_from_slice(b"tail");
12312
12313 let mut input = encoded.as_slice();
12314 assert_eq!(
12315 read_receive_pack_report_status_v2(ObjectFormat::Sha1, &mut input)
12316 .expect("test operation should succeed"),
12317 report
12318 );
12319 assert_eq!(input, b"tail");
12320 }
12321
12322 #[test]
12323 fn receive_pack_report_status_v2_rejects_malformed_options() {
12324 assert!(parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &[]).is_err());
12325 assert!(
12326 parse_receive_pack_report_status_v2(
12327 ObjectFormat::Sha1,
12328 &[
12329 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12330 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12331 PktLineFrame::Flush,
12332 ],
12333 )
12334 .is_err()
12335 );
12336 assert!(
12337 parse_receive_pack_report_status_v2(
12338 ObjectFormat::Sha1,
12339 &[
12340 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12341 PktLineFrame::Data(b"ng refs/heads/main rejected\n".to_vec()),
12342 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12343 PktLineFrame::Flush,
12344 ],
12345 )
12346 .is_err()
12347 );
12348 assert!(
12349 parse_receive_pack_report_status_v2(
12350 ObjectFormat::Sha1,
12351 &[
12352 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12353 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12354 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12355 PktLineFrame::Data(b"option refname refs/heads/next\n".to_vec()),
12356 PktLineFrame::Flush,
12357 ],
12358 )
12359 .is_err()
12360 );
12361 assert!(
12362 parse_receive_pack_report_status_v2(
12363 ObjectFormat::Sha1,
12364 &[
12365 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12366 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12367 PktLineFrame::Data(b"option old-oid not-an-oid\n".to_vec()),
12368 PktLineFrame::Flush,
12369 ],
12370 )
12371 .is_err()
12372 );
12373 assert!(
12374 encode_receive_pack_report_status_v2(&ReceivePackReportStatusV2 {
12375 unpack: ReceivePackUnpackStatus::Ok,
12376 commands: vec![ReceivePackCommandStatusV2::Ok {
12377 name: "refs/for/main".into(),
12378 options: ReceivePackCommandStatusV2Options {
12379 refname: Some("bad ref".into()),
12380 ..ReceivePackCommandStatusV2Options::default()
12381 },
12382 }],
12383 })
12384 .is_err()
12385 );
12386 }
12387
12388 #[test]
12389 fn receive_pack_push_options_parse_and_encode_options() {
12390 let frames = vec![
12391 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12392 PktLineFrame::Data(b"deploy target=staging\n".to_vec()),
12393 PktLineFrame::Data(b"\n".to_vec()),
12394 PktLineFrame::Flush,
12395 ];
12396 let options =
12397 parse_receive_pack_push_options(&frames).expect("test operation should succeed");
12398 assert_eq!(
12399 options,
12400 vec![
12401 "ci.skip".to_string(),
12402 "deploy target=staging".to_string(),
12403 String::new(),
12404 ]
12405 );
12406 assert_eq!(
12407 encode_receive_pack_push_options(&options).expect("test operation should succeed"),
12408 frames
12409 );
12410 assert_eq!(
12411 parse_receive_pack_push_options(&[PktLineFrame::Flush])
12412 .expect("test operation should succeed"),
12413 Vec::<String>::new()
12414 );
12415 }
12416
12417 #[test]
12418 fn receive_pack_push_options_streams_round_trip() {
12419 let options = vec!["ci.skip".to_string(), "reviewer=alice".to_string()];
12420 let mut encoded = Vec::new();
12421 write_receive_pack_push_options(&mut encoded, &options)
12422 .expect("test operation should succeed");
12423 encoded.extend_from_slice(b"PACK");
12424
12425 let mut input = encoded.as_slice();
12426 assert_eq!(
12427 read_receive_pack_push_options(&mut input).expect("test operation should succeed"),
12428 options
12429 );
12430 assert_eq!(input, b"PACK");
12431 }
12432
12433 #[test]
12434 fn receive_pack_push_options_reject_malformed_streams() {
12435 assert!(
12436 parse_receive_pack_push_options(&[PktLineFrame::Data(b"ci.skip\n".to_vec())]).is_err()
12437 );
12438 assert!(
12439 parse_receive_pack_push_options(&[PktLineFrame::Delimiter, PktLineFrame::Flush])
12440 .is_err()
12441 );
12442 assert!(
12443 parse_receive_pack_push_options(&[
12444 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12445 PktLineFrame::Flush,
12446 PktLineFrame::Data(b"after\n".to_vec()),
12447 ])
12448 .is_err()
12449 );
12450 assert!(
12451 parse_receive_pack_push_options(&[
12452 PktLineFrame::Data(b"bad\0option\n".to_vec()),
12453 PktLineFrame::Flush,
12454 ])
12455 .is_err()
12456 );
12457 assert!(encode_receive_pack_push_options(&["bad\noption".to_string()]).is_err());
12458 }
12459
12460 #[test]
12461 fn protocol_v2_advertisement_parses_version_and_capabilities() {
12462 let frames = parse_pkt_line_stream(
12463 b"000eversion 2\n0015agent=git/2.54.0\n0013ls-refs=unborn\n0027fetch=shallow wait-for-done filter\n0012server-option\n0000",
12464 )
12465 .expect("test operation should succeed");
12466 let handshake =
12467 parse_protocol_v2_advertisement(&frames).expect("test operation should succeed");
12468 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12469 assert_eq!(
12470 handshake.capabilities,
12471 vec![
12472 Capability {
12473 name: "agent".into(),
12474 value: Some("git/2.54.0".into()),
12475 },
12476 Capability {
12477 name: "ls-refs".into(),
12478 value: Some("unborn".into()),
12479 },
12480 Capability {
12481 name: "fetch".into(),
12482 value: Some("shallow wait-for-done filter".into()),
12483 },
12484 Capability {
12485 name: "server-option".into(),
12486 value: None,
12487 },
12488 ]
12489 );
12490 assert_eq!(
12491 encode_protocol_v2_advertisement(&handshake).expect("test operation should succeed"),
12492 frames
12493 );
12494 }
12495
12496 #[test]
12497 fn protocol_v2_advertisement_reads_until_flush() {
12498 let mut input = b"000eversion 2\n0013ls-refs=unborn\n0000next-session".as_slice();
12499 let handshake =
12500 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed");
12501 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12502 assert_eq!(
12503 handshake.capabilities,
12504 vec![Capability {
12505 name: "ls-refs".into(),
12506 value: Some("unborn".into()),
12507 }]
12508 );
12509 assert_eq!(input, b"next-session");
12510 }
12511
12512 #[test]
12513 fn protocol_v2_advertisement_writes_stream() {
12514 let handshake = TransportHandshake {
12515 protocol: ProtocolVersion::V2,
12516 capabilities: vec![
12517 Capability {
12518 name: "agent".into(),
12519 value: Some("sley/0".into()),
12520 },
12521 Capability {
12522 name: "fetch".into(),
12523 value: Some("shallow filter".into()),
12524 },
12525 ],
12526 };
12527 let mut encoded = Vec::new();
12528 write_protocol_v2_advertisement(&mut encoded, &handshake)
12529 .expect("test operation should succeed");
12530 let mut input = encoded.as_slice();
12531 assert_eq!(
12532 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed"),
12533 handshake
12534 );
12535 assert!(input.is_empty());
12536 assert!(
12537 encode_protocol_v2_advertisement(&TransportHandshake {
12538 protocol: ProtocolVersion::V1,
12539 capabilities: Vec::new(),
12540 })
12541 .is_err()
12542 );
12543 }
12544
12545 #[test]
12546 fn protocol_v2_advertisement_rejects_malformed_sequences() {
12547 assert!(parse_protocol_v2_advertisement(&[]).is_err());
12548 assert!(
12549 parse_protocol_v2_advertisement(&[
12550 PktLineFrame::Data(b"version 1\n".to_vec()),
12551 PktLineFrame::Flush,
12552 ])
12553 .is_err()
12554 );
12555 assert!(
12556 parse_protocol_v2_advertisement(&[PktLineFrame::Data(b"version 2\n".to_vec())])
12557 .is_err()
12558 );
12559 assert!(
12560 parse_protocol_v2_advertisement(&[
12561 PktLineFrame::Data(b"version 2\n".to_vec()),
12562 PktLineFrame::Delimiter,
12563 ])
12564 .is_err()
12565 );
12566 assert!(
12567 parse_protocol_v2_advertisement(&[
12568 PktLineFrame::Data(b"version 2\n".to_vec()),
12569 PktLineFrame::Data(b"fetch=\n".to_vec()),
12570 PktLineFrame::Flush,
12571 ])
12572 .is_err()
12573 );
12574 }
12575
12576 #[test]
12577 fn protocol_v2_command_request_parses_and_encodes_sections() {
12578 let frames = parse_pkt_line_stream(
12579 b"0014command=ls-refs\n0011agent=sley/0\n0017object-format=sha1\n00010009peel\n000csymrefs\n001bref-prefix refs/heads/\n0000",
12580 )
12581 .expect("test operation should succeed");
12582 let request =
12583 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12584 assert_eq!(
12585 request,
12586 ProtocolV2CommandRequest {
12587 command: "ls-refs".into(),
12588 capabilities: vec![
12589 Capability {
12590 name: "agent".into(),
12591 value: Some("sley/0".into()),
12592 },
12593 Capability {
12594 name: "object-format".into(),
12595 value: Some("sha1".into()),
12596 },
12597 ],
12598 arguments: vec![
12599 b"peel".to_vec(),
12600 b"symrefs".to_vec(),
12601 b"ref-prefix refs/heads/".to_vec(),
12602 ],
12603 }
12604 );
12605 assert_eq!(
12606 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12607 frames
12608 );
12609 }
12610
12611 #[test]
12612 fn protocol_v2_command_request_allows_no_argument_section() {
12613 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12614 .expect("test operation should succeed");
12615 let request =
12616 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12617 assert_eq!(
12618 request,
12619 ProtocolV2CommandRequest {
12620 command: "fetch".into(),
12621 capabilities: Vec::new(),
12622 arguments: Vec::new(),
12623 }
12624 );
12625 assert_eq!(
12626 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12627 frames
12628 );
12629 }
12630
12631 #[test]
12632 fn protocol_v2_request_parses_commands_and_empty_done() {
12633 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12634 .expect("test operation should succeed");
12635 let command = ProtocolV2CommandRequest {
12636 command: "fetch".into(),
12637 capabilities: Vec::new(),
12638 arguments: Vec::new(),
12639 };
12640 assert_eq!(
12641 parse_protocol_v2_request(&frames).expect("test operation should succeed"),
12642 ProtocolV2Request::Command(command.clone())
12643 );
12644 assert_eq!(
12645 encode_protocol_v2_request(&ProtocolV2Request::Command(command))
12646 .expect("test operation should succeed"),
12647 frames
12648 );
12649
12650 assert_eq!(
12651 parse_protocol_v2_request(&[PktLineFrame::Flush])
12652 .expect("test operation should succeed"),
12653 ProtocolV2Request::Done
12654 );
12655 assert_eq!(
12656 encode_protocol_v2_request(&ProtocolV2Request::Done)
12657 .expect("test operation should succeed"),
12658 vec![PktLineFrame::Flush]
12659 );
12660 }
12661
12662 #[test]
12663 fn protocol_v2_request_streams_empty_done() {
12664 let mut encoded = Vec::new();
12665 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
12666 .expect("test operation should succeed");
12667 encoded.extend_from_slice(b"tail");
12668
12669 let mut input = encoded.as_slice();
12670 assert_eq!(
12671 read_protocol_v2_request(&mut input).expect("test operation should succeed"),
12672 ProtocolV2Request::Done
12673 );
12674 assert_eq!(input, b"tail");
12675 let mut command_input = encoded.as_slice();
12676 assert!(read_protocol_v2_command_request(&mut command_input).is_err());
12677 }
12678
12679 #[test]
12680 fn protocol_v2_command_request_streams_round_trip() {
12681 let request = ProtocolV2CommandRequest {
12682 command: "ls-refs".into(),
12683 capabilities: vec![Capability {
12684 name: "agent".into(),
12685 value: Some("sley/0".into()),
12686 }],
12687 arguments: vec![b"peel".to_vec(), b"symrefs".to_vec()],
12688 };
12689 let mut encoded = Vec::new();
12690 write_protocol_v2_command_request(&mut encoded, &request)
12691 .expect("test operation should succeed");
12692 encoded.extend_from_slice(b"tail");
12693
12694 let mut input = encoded.as_slice();
12695 assert_eq!(
12696 read_protocol_v2_command_request(&mut input).expect("test operation should succeed"),
12697 request
12698 );
12699 assert_eq!(input, b"tail");
12700 }
12701
12702 #[test]
12703 fn protocol_v2_command_request_rejects_malformed_sequences() {
12704 assert!(parse_protocol_v2_command_request(&[]).is_err());
12705 assert!(
12706 parse_protocol_v2_command_request(&[
12707 PktLineFrame::Data(b"agent=sley/0\n".to_vec()),
12708 PktLineFrame::Flush,
12709 ])
12710 .is_err()
12711 );
12712 assert!(
12713 parse_protocol_v2_command_request(&[
12714 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12715 PktLineFrame::Delimiter,
12716 PktLineFrame::Delimiter,
12717 PktLineFrame::Flush,
12718 ])
12719 .is_err()
12720 );
12721 assert!(
12722 parse_protocol_v2_command_request(&[
12723 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12724 PktLineFrame::Delimiter,
12725 PktLineFrame::Data(b"\n".to_vec()),
12726 PktLineFrame::Flush,
12727 ])
12728 .is_err()
12729 );
12730 assert!(
12731 encode_protocol_v2_command_request(&ProtocolV2CommandRequest {
12732 command: "bad command".into(),
12733 capabilities: Vec::new(),
12734 arguments: Vec::new(),
12735 })
12736 .is_err()
12737 );
12738 }
12739
12740 #[test]
12741 fn protocol_v2_ls_refs_request_parses_and_encodes_arguments() {
12742 let command = ProtocolV2CommandRequest {
12743 command: "ls-refs".into(),
12744 capabilities: Vec::new(),
12745 arguments: vec![
12746 b"peel".to_vec(),
12747 b"symrefs".to_vec(),
12748 b"unborn".to_vec(),
12749 b"ref-prefix HEAD".to_vec(),
12750 b"ref-prefix refs/heads/".to_vec(),
12751 ],
12752 };
12753 let request = ProtocolV2LsRefsRequest::from_command_request(&command)
12754 .expect("test operation should succeed");
12755 assert_eq!(
12756 request,
12757 ProtocolV2LsRefsRequest {
12758 peel: true,
12759 symrefs: true,
12760 unborn: true,
12761 ref_prefixes: vec!["HEAD".into(), "refs/heads/".into()],
12762 }
12763 );
12764 assert_eq!(
12765 request
12766 .to_command_request()
12767 .expect("test operation should succeed"),
12768 command
12769 );
12770 assert!(
12771 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12772 command: "fetch".into(),
12773 capabilities: Vec::new(),
12774 arguments: Vec::new(),
12775 })
12776 .is_err()
12777 );
12778 assert!(
12779 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12780 command: "ls-refs".into(),
12781 capabilities: Vec::new(),
12782 arguments: vec![b"ref-prefix ".to_vec()],
12783 })
12784 .is_err()
12785 );
12786 }
12787
12788 #[test]
12789 fn protocol_v2_ls_refs_request_streams_round_trip() {
12790 let request = ProtocolV2LsRefsRequest {
12791 peel: true,
12792 symrefs: true,
12793 unborn: false,
12794 ref_prefixes: vec!["HEAD".into(), "refs/tags/".into()],
12795 };
12796 let mut encoded = Vec::new();
12797 write_protocol_v2_ls_refs_request(&mut encoded, &request)
12798 .expect("test operation should succeed");
12799 encoded.extend_from_slice(b"tail");
12800
12801 let mut input = encoded.as_slice();
12802 assert_eq!(
12803 read_protocol_v2_ls_refs_request(&mut input).expect("test operation should succeed"),
12804 request
12805 );
12806 assert_eq!(input, b"tail");
12807 }
12808
12809 #[test]
12810 fn protocol_v2_ls_refs_response_parses_and_encodes_records() {
12811 let oid = ObjectId::from_hex(
12812 ObjectFormat::Sha1,
12813 "1111111111111111111111111111111111111111",
12814 )
12815 .expect("test operation should succeed");
12816 let peeled = ObjectId::from_hex(
12817 ObjectFormat::Sha1,
12818 "2222222222222222222222222222222222222222",
12819 )
12820 .expect("test operation should succeed");
12821 let frames = vec![
12822 PktLineFrame::Data(
12823 b"1111111111111111111111111111111111111111 refs/tags/v1 peeled:2222222222222222222222222222222222222222 symref-target:refs/heads/main custom\n"
12824 .to_vec(),
12825 ),
12826 PktLineFrame::Data(b"unborn HEAD symref-target:refs/heads/main\n".to_vec()),
12827 PktLineFrame::Flush,
12828 ];
12829 let records = parse_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &frames)
12830 .expect("test operation should succeed");
12831 assert_eq!(
12832 records,
12833 vec![
12834 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12835 oid,
12836 name: "refs/tags/v1".into(),
12837 peeled: Some(peeled),
12838 symref_target: Some("refs/heads/main".into()),
12839 attributes: vec!["custom".into()],
12840 }),
12841 ProtocolV2LsRefsRecord::Unborn {
12842 name: "HEAD".into(),
12843 symref_target: Some("refs/heads/main".into()),
12844 attributes: Vec::new(),
12845 },
12846 ]
12847 );
12848 assert_eq!(
12849 encode_protocol_v2_ls_refs_response(&records).expect("test operation should succeed"),
12850 frames
12851 );
12852 }
12853
12854 #[test]
12855 fn protocol_v2_ls_refs_response_streams_round_trip() {
12856 let oid = ObjectId::from_hex(
12857 ObjectFormat::Sha1,
12858 "1111111111111111111111111111111111111111",
12859 )
12860 .expect("test operation should succeed");
12861 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12862 oid,
12863 name: "refs/heads/main".into(),
12864 peeled: None,
12865 symref_target: Some("refs/heads/trunk".into()),
12866 attributes: vec!["custom".into()],
12867 })];
12868 let mut encoded = Vec::new();
12869 write_protocol_v2_ls_refs_response(&mut encoded, &records)
12870 .expect("test operation should succeed");
12871 encoded.extend_from_slice(b"tail");
12872
12873 let mut input = encoded.as_slice();
12874 assert_eq!(
12875 read_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &mut input)
12876 .expect("test operation should succeed"),
12877 records
12878 );
12879 assert_eq!(input, b"tail");
12880 }
12881
12882 #[test]
12883 fn protocol_v2_ls_refs_response_reads_stateless_response_end() {
12884 let oid = ObjectId::from_hex(
12885 ObjectFormat::Sha1,
12886 "1111111111111111111111111111111111111111",
12887 )
12888 .expect("test operation should succeed");
12889 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12890 oid,
12891 name: "refs/heads/main".into(),
12892 peeled: None,
12893 symref_target: None,
12894 attributes: Vec::new(),
12895 })];
12896 let mut encoded = Vec::new();
12897 write_protocol_v2_ls_refs_response_with_response_end(&mut encoded, &records)
12898 .expect("test operation should succeed");
12899 encoded.extend_from_slice(b"tail");
12900
12901 let mut input = encoded.as_slice();
12902 assert_eq!(
12903 read_protocol_v2_ls_refs_response_until_response_end(ObjectFormat::Sha1, &mut input)
12904 .expect("test operation should succeed"),
12905 records
12906 );
12907 assert_eq!(input, b"tail");
12908 assert!(
12909 parse_protocol_v2_ls_refs_response(
12910 ObjectFormat::Sha1,
12911 &[
12912 PktLineFrame::Data(
12913 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
12914 ),
12915 PktLineFrame::ResponseEnd
12916 ],
12917 )
12918 .is_err()
12919 );
12920 }
12921
12922 #[test]
12923 fn protocol_v2_ls_refs_exchange_writes_request_and_reads_response() {
12924 let oid = ObjectId::from_hex(
12925 ObjectFormat::Sha1,
12926 "1111111111111111111111111111111111111111",
12927 )
12928 .expect("test operation should succeed");
12929 let request = ProtocolV2LsRefsRequest {
12930 peel: true,
12931 symrefs: true,
12932 unborn: false,
12933 ref_prefixes: vec!["refs/heads/".into()],
12934 };
12935 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12936 oid,
12937 name: "refs/heads/main".into(),
12938 peeled: None,
12939 symref_target: None,
12940 attributes: Vec::new(),
12941 })];
12942 let mut response = Vec::new();
12943 write_protocol_v2_ls_refs_response(&mut response, &records)
12944 .expect("test operation should succeed");
12945
12946 let mut input = response.as_slice();
12947 let mut output = Vec::new();
12948 assert_eq!(
12949 exchange_protocol_v2_ls_refs(ObjectFormat::Sha1, &mut input, &mut output, &request)
12950 .expect("test operation should succeed"),
12951 records
12952 );
12953 assert!(input.is_empty());
12954 let mut output_read = output.as_slice();
12955 assert_eq!(
12956 read_protocol_v2_ls_refs_request(&mut output_read)
12957 .expect("test operation should succeed"),
12958 request
12959 );
12960 }
12961
12962 #[test]
12963 fn protocol_v2_ls_refs_response_rejects_malformed_records() {
12964 assert!(
12965 parse_protocol_v2_ls_refs_response(
12966 ObjectFormat::Sha1,
12967 &[PktLineFrame::Data(
12968 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
12969 )],
12970 )
12971 .is_err()
12972 );
12973 assert!(
12974 parse_protocol_v2_ls_refs_response(
12975 ObjectFormat::Sha1,
12976 &[
12977 PktLineFrame::Data(
12978 b"1111111111111111111111111111111111111111 refs/heads/main peeled:2222222222222222222222222222222222222222 peeled:3333333333333333333333333333333333333333\n"
12979 .to_vec()
12980 ),
12981 PktLineFrame::Flush,
12982 ],
12983 )
12984 .is_err()
12985 );
12986 assert!(
12987 parse_protocol_v2_ls_refs_response(
12988 ObjectFormat::Sha1,
12989 &[
12990 PktLineFrame::Data(
12991 b"unborn HEAD peeled:2222222222222222222222222222222222222222\n".to_vec()
12992 ),
12993 PktLineFrame::Flush,
12994 ],
12995 )
12996 .is_err()
12997 );
12998 assert!(
12999 encode_protocol_v2_ls_refs_response(&[ProtocolV2LsRefsRecord::Ref(
13000 ProtocolV2LsRefsRef {
13001 oid: ObjectId::from_hex(
13002 ObjectFormat::Sha1,
13003 "1111111111111111111111111111111111111111",
13004 )
13005 .expect("test operation should succeed"),
13006 name: "refs/heads/main".into(),
13007 peeled: None,
13008 symref_target: None,
13009 attributes: vec!["peeled:2222222222222222222222222222222222222222".into()],
13010 }
13011 )])
13012 .is_err()
13013 );
13014 }
13015
13016 #[test]
13017 fn protocol_v2_fetch_request_parses_and_encodes_arguments() {
13018 let want = ObjectId::from_hex(
13019 ObjectFormat::Sha1,
13020 "1111111111111111111111111111111111111111",
13021 )
13022 .expect("test operation should succeed");
13023 let have = ObjectId::from_hex(
13024 ObjectFormat::Sha1,
13025 "2222222222222222222222222222222222222222",
13026 )
13027 .expect("test operation should succeed");
13028 let shallow = ObjectId::from_hex(
13029 ObjectFormat::Sha1,
13030 "3333333333333333333333333333333333333333",
13031 )
13032 .expect("test operation should succeed");
13033 let command = ProtocolV2CommandRequest {
13034 command: "fetch".into(),
13035 capabilities: Vec::new(),
13036 arguments: vec![
13037 b"want 1111111111111111111111111111111111111111".to_vec(),
13038 b"want-ref refs/heads/main".to_vec(),
13039 b"have 2222222222222222222222222222222222222222".to_vec(),
13040 b"shallow 3333333333333333333333333333333333333333".to_vec(),
13041 b"deepen 10".to_vec(),
13042 b"deepen-since 123456789".to_vec(),
13043 b"deepen-not refs/tags/v1".to_vec(),
13044 b"deepen-relative".to_vec(),
13045 b"filter blob:none".to_vec(),
13046 b"packfile-uris http,https".to_vec(),
13047 b"thin-pack".to_vec(),
13048 b"no-progress".to_vec(),
13049 b"include-tag".to_vec(),
13050 b"ofs-delta".to_vec(),
13051 b"sideband-all".to_vec(),
13052 b"wait-for-done".to_vec(),
13053 b"done".to_vec(),
13054 ],
13055 };
13056 let request = ProtocolV2FetchRequest::from_command_request(ObjectFormat::Sha1, &command)
13057 .expect("test operation should succeed");
13058 assert_eq!(
13059 request,
13060 ProtocolV2FetchRequest {
13061 wants: vec![want],
13062 want_refs: vec!["refs/heads/main".into()],
13063 haves: vec![have],
13064 shallow: vec![shallow],
13065 deepen: Some(10),
13066 deepen_since: Some(123456789),
13067 deepen_not: vec!["refs/tags/v1".into()],
13068 deepen_relative: true,
13069 filter: Some("blob:none".into()),
13070 packfile_uris: Some("http,https".into()),
13071 thin_pack: true,
13072 no_progress: true,
13073 include_tag: true,
13074 ofs_delta: true,
13075 sideband_all: true,
13076 wait_for_done: true,
13077 done: true,
13078 }
13079 );
13080 assert_eq!(
13081 request
13082 .to_command_request()
13083 .expect("test operation should succeed"),
13084 command
13085 );
13086 }
13087
13088 #[test]
13089 fn protocol_v2_fetch_request_rejects_malformed_arguments() {
13090 assert!(
13091 ProtocolV2FetchRequest::from_command_request(
13092 ObjectFormat::Sha1,
13093 &ProtocolV2CommandRequest {
13094 command: "ls-refs".into(),
13095 capabilities: Vec::new(),
13096 arguments: Vec::new(),
13097 },
13098 )
13099 .is_err()
13100 );
13101 assert!(
13102 ProtocolV2FetchRequest::from_command_request(
13103 ObjectFormat::Sha1,
13104 &ProtocolV2CommandRequest {
13105 command: "fetch".into(),
13106 capabilities: Vec::new(),
13107 arguments: vec![b"want not-an-oid".to_vec()],
13108 },
13109 )
13110 .is_err()
13111 );
13112 assert!(
13113 ProtocolV2FetchRequest::from_command_request(
13114 ObjectFormat::Sha1,
13115 &ProtocolV2CommandRequest {
13116 command: "fetch".into(),
13117 capabilities: Vec::new(),
13118 arguments: vec![b"deepen 0".to_vec()],
13119 },
13120 )
13121 .is_err()
13122 );
13123 assert!(
13124 ProtocolV2FetchRequest::from_command_request(
13125 ObjectFormat::Sha1,
13126 &ProtocolV2CommandRequest {
13127 command: "fetch".into(),
13128 capabilities: Vec::new(),
13129 arguments: vec![b"filter blob:none".to_vec(), b"filter tree:0".to_vec()],
13130 },
13131 )
13132 .is_err()
13133 );
13134 assert!(
13135 ProtocolV2FetchRequest {
13136 deepen: Some(0),
13137 ..ProtocolV2FetchRequest::default()
13138 }
13139 .to_command_request()
13140 .is_err()
13141 );
13142 }
13143
13144 #[test]
13145 fn protocol_v2_fetch_request_streams_round_trip() {
13146 let want = ObjectId::from_hex(
13147 ObjectFormat::Sha1,
13148 "1111111111111111111111111111111111111111",
13149 )
13150 .expect("test operation should succeed");
13151 let have = ObjectId::from_hex(
13152 ObjectFormat::Sha1,
13153 "2222222222222222222222222222222222222222",
13154 )
13155 .expect("test operation should succeed");
13156 let request = ProtocolV2FetchRequest {
13157 wants: vec![want],
13158 haves: vec![have],
13159 deepen: Some(5),
13160 filter: Some("blob:none".into()),
13161 thin_pack: true,
13162 done: true,
13163 ..ProtocolV2FetchRequest::default()
13164 };
13165 let mut encoded = Vec::new();
13166 write_protocol_v2_fetch_request(&mut encoded, &request)
13167 .expect("test operation should succeed");
13168 encoded.extend_from_slice(b"tail");
13169
13170 let mut input = encoded.as_slice();
13171 assert_eq!(
13172 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut input)
13173 .expect("test operation should succeed"),
13174 request
13175 );
13176 assert_eq!(input, b"tail");
13177 }
13178
13179 #[test]
13180 fn protocol_v2_fetch_response_parses_and_encodes_sections() {
13181 let ack = ObjectId::from_hex(
13182 ObjectFormat::Sha1,
13183 "1111111111111111111111111111111111111111",
13184 )
13185 .expect("test operation should succeed");
13186 let shallow = ObjectId::from_hex(
13187 ObjectFormat::Sha1,
13188 "2222222222222222222222222222222222222222",
13189 )
13190 .expect("test operation should succeed");
13191 let wanted = ObjectId::from_hex(
13192 ObjectFormat::Sha1,
13193 "3333333333333333333333333333333333333333",
13194 )
13195 .expect("test operation should succeed");
13196 let pack_hash = ObjectId::from_hex(
13197 ObjectFormat::Sha1,
13198 "4444444444444444444444444444444444444444",
13199 )
13200 .expect("test operation should succeed");
13201 let frames = vec![
13202 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13203 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111\n".to_vec()),
13204 PktLineFrame::Data(b"ready\n".to_vec()),
13205 PktLineFrame::Delimiter,
13206 PktLineFrame::Data(b"shallow-info\n".to_vec()),
13207 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
13208 PktLineFrame::Delimiter,
13209 PktLineFrame::Data(b"wanted-refs\n".to_vec()),
13210 PktLineFrame::Data(
13211 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
13212 ),
13213 PktLineFrame::Delimiter,
13214 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13215 PktLineFrame::Data(
13216 b"4444444444444444444444444444444444444444 https://example.invalid/pack-a.pack\n"
13217 .to_vec(),
13218 ),
13219 PktLineFrame::Delimiter,
13220 PktLineFrame::Data(b"packfile\n".to_vec()),
13221 PktLineFrame::Data(b"\x01PACK bytes".to_vec()),
13222 PktLineFrame::Flush,
13223 ];
13224 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13225 .expect("test operation should succeed");
13226 assert_eq!(
13227 sections,
13228 vec![
13229 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13230 ProtocolV2FetchAcknowledgment::Ack(ack),
13231 ProtocolV2FetchAcknowledgment::Ready,
13232 ]),
13233 ProtocolV2FetchResponseSection::ShallowInfo(vec![
13234 ProtocolV2FetchShallowInfo::Shallow(shallow)
13235 ]),
13236 ProtocolV2FetchResponseSection::WantedRefs(vec![ProtocolV2FetchWantedRef {
13237 oid: wanted,
13238 name: "refs/heads/main".into(),
13239 }]),
13240 ProtocolV2FetchResponseSection::PackfileUris(vec![ProtocolV2FetchPackfileUri {
13241 pack_hash,
13242 uri: "https://example.invalid/pack-a.pack".into(),
13243 }]),
13244 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13245 ]
13246 );
13247 assert_eq!(
13248 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13249 frames
13250 );
13251 }
13252
13253 #[test]
13254 fn protocol_v2_fetch_response_preserves_unknown_sections() {
13255 let frames = vec![
13256 PktLineFrame::Data(b"server-feature\n".to_vec()),
13257 PktLineFrame::Data(b"opaque line\n".to_vec()),
13258 PktLineFrame::Flush,
13259 ];
13260 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13261 .expect("test operation should succeed");
13262 assert_eq!(
13263 sections,
13264 vec![ProtocolV2FetchResponseSection::Unknown {
13265 name: "server-feature".into(),
13266 lines: vec![b"opaque line\n".to_vec()],
13267 }]
13268 );
13269 assert_eq!(
13270 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13271 frames
13272 );
13273 }
13274
13275 #[test]
13276 fn protocol_v2_fetch_response_streams_round_trip() {
13277 let ack = ObjectId::from_hex(
13278 ObjectFormat::Sha1,
13279 "1111111111111111111111111111111111111111",
13280 )
13281 .expect("test operation should succeed");
13282 let sections = vec![
13283 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13284 ProtocolV2FetchAcknowledgment::Ack(ack),
13285 ProtocolV2FetchAcknowledgment::Ready,
13286 ]),
13287 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13288 ];
13289 let mut encoded = Vec::new();
13290 write_protocol_v2_fetch_response(&mut encoded, §ions)
13291 .expect("test operation should succeed");
13292 encoded.extend_from_slice(b"tail");
13293
13294 let mut input = encoded.as_slice();
13295 assert_eq!(
13296 read_protocol_v2_fetch_response(ObjectFormat::Sha1, &mut input)
13297 .expect("test operation should succeed"),
13298 sections
13299 );
13300 assert_eq!(input, b"tail");
13301 }
13302
13303 #[test]
13304 fn protocol_v2_fetch_sideband_all_response_parses_sections_and_progress() {
13305 let frames = vec![
13306 PktLineFrame::Data(
13307 encode_sideband_packet(&SideBandPacket {
13308 channel: SideBandChannel::Data,
13309 data: b"acknowledgments\n".to_vec(),
13310 })
13311 .expect("test operation should succeed"),
13312 ),
13313 PktLineFrame::Data(
13314 encode_sideband_packet(&SideBandPacket {
13315 channel: SideBandChannel::Data,
13316 data: b"NAK\n".to_vec(),
13317 })
13318 .expect("test operation should succeed"),
13319 ),
13320 PktLineFrame::Data(
13321 encode_sideband_packet(&SideBandPacket {
13322 channel: SideBandChannel::Progress,
13323 data: b"keepalive\n".to_vec(),
13324 })
13325 .expect("test operation should succeed"),
13326 ),
13327 PktLineFrame::Delimiter,
13328 PktLineFrame::Data(
13329 encode_sideband_packet(&SideBandPacket {
13330 channel: SideBandChannel::Data,
13331 data: b"packfile\n".to_vec(),
13332 })
13333 .expect("test operation should succeed"),
13334 ),
13335 PktLineFrame::Data(b"\x01PACK".to_vec()),
13336 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
13337 PktLineFrame::Flush,
13338 ];
13339
13340 let response = parse_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &frames)
13341 .expect("test operation should succeed");
13342 assert_eq!(
13343 response,
13344 ProtocolV2FetchSidebandAllResponse {
13345 sections: vec![
13346 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13347 ProtocolV2FetchAcknowledgment::Nak
13348 ]),
13349 ProtocolV2FetchResponseSection::Packfile(vec![
13350 b"\x01PACK".to_vec(),
13351 b"\x02counting objects\n".to_vec(),
13352 ]),
13353 ],
13354 progress: vec![b"keepalive\n".to_vec()],
13355 }
13356 );
13357 assert_eq!(
13358 demux_protocol_v2_fetch_packfile(&response.sections)
13359 .expect("test operation should succeed"),
13360 Some(SideBandDemux {
13361 data: b"PACK".to_vec(),
13362 progress: vec![b"counting objects\n".to_vec()],
13363 })
13364 );
13365 }
13366
13367 #[test]
13368 fn protocol_v2_fetch_sideband_all_response_streams_round_trip() {
13369 let sections = vec![
13370 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13371 ProtocolV2FetchAcknowledgment::Nak,
13372 ]),
13373 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13374 ];
13375 let mut encoded = Vec::new();
13376 write_protocol_v2_fetch_sideband_all_response(&mut encoded, §ions)
13377 .expect("test operation should succeed");
13378 encoded.extend_from_slice(b"tail");
13379
13380 let mut input = encoded.as_slice();
13381 assert_eq!(
13382 read_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &mut input)
13383 .expect("test operation should succeed"),
13384 ProtocolV2FetchSidebandAllResponse {
13385 sections: sections.clone(),
13386 progress: Vec::new(),
13387 }
13388 );
13389 assert_eq!(input, b"tail");
13390
13391 let mut encoded = Vec::new();
13392 write_protocol_v2_fetch_sideband_all_response_with_response_end(&mut encoded, §ions)
13393 .expect("test operation should succeed");
13394 encoded.extend_from_slice(b"tail");
13395
13396 let mut input = encoded.as_slice();
13397 assert_eq!(
13398 read_protocol_v2_fetch_sideband_all_response_until_response_end(
13399 ObjectFormat::Sha1,
13400 &mut input,
13401 )
13402 .expect("test operation should succeed")
13403 .sections,
13404 sections
13405 );
13406 assert_eq!(input, b"tail");
13407 }
13408
13409 #[test]
13410 fn protocol_v2_fetch_sideband_all_response_rejects_malformed_sideband() {
13411 assert!(
13412 parse_protocol_v2_fetch_sideband_all_response(
13413 ObjectFormat::Sha1,
13414 &[
13415 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13416 PktLineFrame::Flush,
13417 ],
13418 )
13419 .is_err()
13420 );
13421 assert!(
13422 parse_protocol_v2_fetch_sideband_all_response(
13423 ObjectFormat::Sha1,
13424 &[
13425 PktLineFrame::Data(
13426 encode_sideband_packet(&SideBandPacket {
13427 channel: SideBandChannel::Fatal,
13428 data: b"remote died\n".to_vec(),
13429 })
13430 .expect("test operation should succeed"),
13431 ),
13432 PktLineFrame::Flush,
13433 ],
13434 )
13435 .is_err()
13436 );
13437 }
13438
13439 #[test]
13440 fn protocol_v2_object_info_response_parses_and_encodes_size_records() {
13441 let oid = ObjectId::from_hex(
13442 ObjectFormat::Sha1,
13443 "1111111111111111111111111111111111111111",
13444 )
13445 .expect("test operation should succeed");
13446 let frames = vec![
13447 PktLineFrame::Data(b"size\n".to_vec()),
13448 PktLineFrame::Data(b"1111111111111111111111111111111111111111 12345\n".to_vec()),
13449 PktLineFrame::Flush,
13450 ];
13451 let response = parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &frames)
13452 .expect("test operation should succeed");
13453 assert_eq!(
13454 response,
13455 ProtocolV2ObjectInfoResponse {
13456 size: true,
13457 records: vec![ProtocolV2ObjectInfoRecord { oid, size: 12345 }],
13458 }
13459 );
13460 assert_eq!(
13461 encode_protocol_v2_object_info_response(&response)
13462 .expect("test operation should succeed"),
13463 frames
13464 );
13465 }
13466
13467 #[test]
13468 fn protocol_v2_object_info_response_streams_and_exchanges() {
13469 let request = ProtocolV2ObjectInfoRequest {
13470 size: true,
13471 oids: vec![
13472 ObjectId::from_hex(
13473 ObjectFormat::Sha1,
13474 "1111111111111111111111111111111111111111",
13475 )
13476 .expect("test operation should succeed"),
13477 ],
13478 };
13479 let response = ProtocolV2ObjectInfoResponse {
13480 size: true,
13481 records: vec![ProtocolV2ObjectInfoRecord {
13482 oid: request.oids[0].clone(),
13483 size: 7,
13484 }],
13485 };
13486
13487 let mut encoded = Vec::new();
13488 write_protocol_v2_object_info_response(&mut encoded, &response)
13489 .expect("test operation should succeed");
13490 encoded.extend_from_slice(b"tail");
13491 let mut input = encoded.as_slice();
13492 assert_eq!(
13493 read_protocol_v2_object_info_response(ObjectFormat::Sha1, &mut input)
13494 .expect("test operation should succeed"),
13495 response
13496 );
13497 assert_eq!(input, b"tail");
13498
13499 let mut response_bytes = Vec::new();
13500 write_protocol_v2_object_info_response(&mut response_bytes, &response)
13501 .expect("test operation should succeed");
13502 let mut input = response_bytes.as_slice();
13503 let mut output = Vec::new();
13504 assert_eq!(
13505 exchange_protocol_v2_object_info(
13506 ObjectFormat::Sha1,
13507 &mut input,
13508 &mut output,
13509 &request,
13510 )
13511 .expect("test operation should succeed"),
13512 response
13513 );
13514 assert!(input.is_empty());
13515 let mut output_read = output.as_slice();
13516 assert_eq!(
13517 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut output_read)
13518 .expect("test operation should succeed"),
13519 request
13520 );
13521 }
13522
13523 #[test]
13524 fn protocol_v2_object_info_response_rejects_malformed_records() {
13525 assert!(parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &[]).is_err());
13526 assert!(
13527 parse_protocol_v2_object_info_response(
13528 ObjectFormat::Sha1,
13529 &[PktLineFrame::Data(b"size\n".to_vec())],
13530 )
13531 .is_err()
13532 );
13533 assert!(
13534 parse_protocol_v2_object_info_response(
13535 ObjectFormat::Sha1,
13536 &[PktLineFrame::Data(b"type\n".to_vec()), PktLineFrame::Flush,],
13537 )
13538 .is_err()
13539 );
13540 assert!(
13541 parse_protocol_v2_object_info_response(
13542 ObjectFormat::Sha1,
13543 &[
13544 PktLineFrame::Data(b"size\n".to_vec()),
13545 PktLineFrame::Data(
13546 b"1111111111111111111111111111111111111111 not-a-size\n".to_vec()
13547 ),
13548 PktLineFrame::Flush,
13549 ],
13550 )
13551 .is_err()
13552 );
13553 assert!(
13554 parse_protocol_v2_object_info_response(
13555 ObjectFormat::Sha1,
13556 &[
13557 PktLineFrame::Data(b"size\n".to_vec()),
13558 PktLineFrame::Delimiter,
13559 PktLineFrame::Flush,
13560 ],
13561 )
13562 .is_err()
13563 );
13564 assert!(
13565 encode_protocol_v2_object_info_response(&ProtocolV2ObjectInfoResponse {
13566 size: false,
13567 records: Vec::new(),
13568 })
13569 .is_err()
13570 );
13571 }
13572
13573 #[test]
13574 fn protocol_v2_fetch_response_reads_stateless_response_end() {
13575 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13576 ProtocolV2FetchAcknowledgment::Nak,
13577 ])];
13578 let mut encoded = Vec::new();
13579 write_protocol_v2_fetch_response_with_response_end(&mut encoded, §ions)
13580 .expect("test operation should succeed");
13581 encoded.extend_from_slice(b"tail");
13582
13583 let mut input = encoded.as_slice();
13584 assert_eq!(
13585 read_protocol_v2_fetch_response_until_response_end(ObjectFormat::Sha1, &mut input)
13586 .expect("test operation should succeed"),
13587 sections
13588 );
13589 assert_eq!(input, b"tail");
13590 assert!(
13591 parse_protocol_v2_fetch_response(
13592 ObjectFormat::Sha1,
13593 &[
13594 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13595 PktLineFrame::ResponseEnd,
13596 ],
13597 )
13598 .is_err()
13599 );
13600 }
13601
13602 #[test]
13603 fn protocol_v2_fetch_exchange_writes_request_and_reads_response() {
13604 let want = ObjectId::from_hex(
13605 ObjectFormat::Sha1,
13606 "1111111111111111111111111111111111111111",
13607 )
13608 .expect("test operation should succeed");
13609 let request = ProtocolV2FetchRequest {
13610 wants: vec![want],
13611 thin_pack: true,
13612 done: true,
13613 ..ProtocolV2FetchRequest::default()
13614 };
13615 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13616 ProtocolV2FetchAcknowledgment::Nak,
13617 ])];
13618 let mut response = Vec::new();
13619 write_protocol_v2_fetch_response(&mut response, §ions)
13620 .expect("test operation should succeed");
13621
13622 let mut input = response.as_slice();
13623 let mut output = Vec::new();
13624 assert_eq!(
13625 exchange_protocol_v2_fetch(ObjectFormat::Sha1, &mut input, &mut output, &request)
13626 .expect("test operation should succeed"),
13627 sections
13628 );
13629 assert!(input.is_empty());
13630 let mut output_read = output.as_slice();
13631 assert_eq!(
13632 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut output_read)
13633 .expect("test operation should succeed"),
13634 request
13635 );
13636 }
13637
13638 #[test]
13639 fn protocol_v2_fetch_packfile_demuxes_sideband_section() {
13640 let sections = vec![
13641 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13642 ProtocolV2FetchAcknowledgment::Nak,
13643 ]),
13644 ProtocolV2FetchResponseSection::Packfile(vec![
13645 b"\x01PACK".to_vec(),
13646 b"\x02counting objects\n".to_vec(),
13647 b"\x01 bytes".to_vec(),
13648 b"\x02done\n".to_vec(),
13649 ]),
13650 ];
13651
13652 assert_eq!(
13653 demux_protocol_v2_fetch_packfile(§ions).expect("test operation should succeed"),
13654 Some(SideBandDemux {
13655 data: b"PACK bytes".to_vec(),
13656 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
13657 })
13658 );
13659 assert_eq!(
13660 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Acknowledgments(
13661 vec![ProtocolV2FetchAcknowledgment::Nak],
13662 )])
13663 .expect("test operation should succeed"),
13664 None
13665 );
13666 }
13667
13668 #[test]
13669 fn protocol_v2_fetch_packfile_demux_rejects_duplicate_or_bad_sideband() {
13670 assert!(
13671 demux_protocol_v2_fetch_packfile(&[
13672 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK".to_vec()]),
13673 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01more".to_vec()]),
13674 ])
13675 .is_err()
13676 );
13677 assert!(
13678 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13679 b"\x03remote died\n".to_vec()
13680 ])])
13681 .is_err()
13682 );
13683 assert!(
13684 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13685 b"\x04bad".to_vec()
13686 ])])
13687 .is_err()
13688 );
13689 }
13690
13691 #[test]
13692 fn protocol_v2_fetch_response_rejects_malformed_sections() {
13693 assert!(
13694 parse_protocol_v2_fetch_response(
13695 ObjectFormat::Sha1,
13696 &[PktLineFrame::Data(b"acknowledgments\n".to_vec())],
13697 )
13698 .is_err()
13699 );
13700 assert!(
13701 parse_protocol_v2_fetch_response(
13702 ObjectFormat::Sha1,
13703 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
13704 )
13705 .is_err()
13706 );
13707 assert!(
13708 parse_protocol_v2_fetch_response(
13709 ObjectFormat::Sha1,
13710 &[
13711 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13712 PktLineFrame::Data(b"ACK not-an-oid\n".to_vec()),
13713 PktLineFrame::Flush,
13714 ],
13715 )
13716 .is_err()
13717 );
13718 assert!(
13719 parse_protocol_v2_fetch_response(
13720 ObjectFormat::Sha1,
13721 &[
13722 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13723 PktLineFrame::Data(b"https://example.invalid/pack-a.pack\n".to_vec()),
13724 PktLineFrame::Flush,
13725 ],
13726 )
13727 .is_err()
13728 );
13729 assert!(
13730 parse_protocol_v2_fetch_response(
13731 ObjectFormat::Sha1,
13732 &[
13733 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13734 PktLineFrame::Data(
13735 b"not-a-hash https://example.invalid/pack-a.pack\n".to_vec()
13736 ),
13737 PktLineFrame::Flush,
13738 ],
13739 )
13740 .is_err()
13741 );
13742 assert!(
13743 encode_protocol_v2_fetch_response(&[ProtocolV2FetchResponseSection::WantedRefs(vec![
13744 ProtocolV2FetchWantedRef {
13745 oid: ObjectId::from_hex(
13746 ObjectFormat::Sha1,
13747 "1111111111111111111111111111111111111111",
13748 )
13749 .expect("test operation should succeed"),
13750 name: "bad ref".into(),
13751 }
13752 ])])
13753 .is_err()
13754 );
13755 }
13756
13757 #[test]
13758 fn protocol_v2_ls_refs_response_bridges_into_ref_advertisement_set() {
13759 let head = ObjectId::from_hex(
13760 ObjectFormat::Sha1,
13761 "1111111111111111111111111111111111111111",
13762 )
13763 .expect("test operation should succeed");
13764 let tag = ObjectId::from_hex(
13765 ObjectFormat::Sha1,
13766 "2222222222222222222222222222222222222222",
13767 )
13768 .expect("test operation should succeed");
13769 let tag_peeled = ObjectId::from_hex(
13770 ObjectFormat::Sha1,
13771 "3333333333333333333333333333333333333333",
13772 )
13773 .expect("test operation should succeed");
13774 let frames = vec![
13775 PktLineFrame::Data(
13776 b"1111111111111111111111111111111111111111 HEAD symref-target:refs/heads/main\n"
13777 .to_vec(),
13778 ),
13779 PktLineFrame::Data(
13780 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
13781 ),
13782 PktLineFrame::Data(
13783 b"2222222222222222222222222222222222222222 refs/tags/v1 peeled:3333333333333333333333333333333333333333\n"
13784 .to_vec(),
13785 ),
13786 PktLineFrame::Flush,
13787 ];
13788
13789 let set = parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13790 ObjectFormat::Sha1,
13791 &frames,
13792 )
13793 .expect("test operation should succeed");
13794 assert_eq!(
13795 set,
13796 RefAdvertisementSet {
13797 protocol: ProtocolVersion::V2,
13798 refs: vec![
13799 RefAdvertisement {
13800 oid: head.clone(),
13801 name: "HEAD".into(),
13802 capabilities: vec![Capability {
13803 name: "symref".into(),
13804 value: Some("HEAD:refs/heads/main".into()),
13805 }],
13806 },
13807 RefAdvertisement {
13808 oid: head,
13809 name: "refs/heads/main".into(),
13810 capabilities: Vec::new(),
13811 },
13812 RefAdvertisement {
13813 oid: tag,
13814 name: "refs/tags/v1".into(),
13815 capabilities: Vec::new(),
13816 },
13817 RefAdvertisement {
13818 oid: tag_peeled,
13819 name: "refs/tags/v1^{}".into(),
13820 capabilities: Vec::new(),
13821 },
13822 ],
13823 shallow: Vec::new(),
13824 }
13825 );
13826
13827 let mut encoded = Vec::new();
13829 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
13830 encoded.extend_from_slice(b"tail");
13831 let mut input = encoded.as_slice();
13832 assert_eq!(
13833 read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13834 ObjectFormat::Sha1,
13835 &mut input,
13836 )
13837 .expect("test operation should succeed"),
13838 set,
13839 );
13840 assert_eq!(input, b"tail");
13841 }
13842
13843 #[test]
13844 fn protocol_v2_ls_refs_records_bridge_unborn_head_symref_and_empty() {
13845 let records = vec![ProtocolV2LsRefsRecord::Unborn {
13848 name: "HEAD".into(),
13849 symref_target: Some("refs/heads/main".into()),
13850 attributes: Vec::new(),
13851 }];
13852 assert!(protocol_v2_ls_refs_records_to_ref_advertisement_set(&records).is_err());
13853
13854 assert_eq!(
13856 protocol_v2_ls_refs_records_to_ref_advertisement_set(&[])
13857 .expect("test operation should succeed"),
13858 RefAdvertisementSet {
13859 protocol: ProtocolVersion::V2,
13860 refs: Vec::new(),
13861 shallow: Vec::new(),
13862 }
13863 );
13864
13865 let main = ObjectId::from_hex(
13868 ObjectFormat::Sha1,
13869 "4444444444444444444444444444444444444444",
13870 )
13871 .expect("test operation should succeed");
13872 let records = vec![
13873 ProtocolV2LsRefsRecord::Unborn {
13874 name: "HEAD".into(),
13875 symref_target: Some("refs/heads/main".into()),
13876 attributes: Vec::new(),
13877 },
13878 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13879 oid: main.clone(),
13880 name: "refs/heads/main".into(),
13881 peeled: None,
13882 symref_target: None,
13883 attributes: Vec::new(),
13884 }),
13885 ];
13886 let set = protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
13887 .expect("test operation should succeed");
13888 assert_eq!(
13889 set,
13890 RefAdvertisementSet {
13891 protocol: ProtocolVersion::V2,
13892 refs: vec![RefAdvertisement {
13893 oid: main,
13894 name: "refs/heads/main".into(),
13895 capabilities: vec![Capability {
13896 name: "symref".into(),
13897 value: Some("HEAD:refs/heads/main".into()),
13898 }],
13899 }],
13900 shallow: Vec::new(),
13901 }
13902 );
13903 }
13904}