1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
4
5use sley_core::{Capability, GitError, ObjectFormat, ObjectId, Result};
6use std::io::{ErrorKind, Read, Write};
7
8pub const PKT_LINE_MAX_LEN: usize = 65_520;
9
10pub const PKT_LINE_MAX_PAYLOAD_LEN: usize = PKT_LINE_MAX_LEN - 4;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ProtocolVersion {
14 V0,
15 V1,
16 V2,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct PktLine(pub Vec<u8>);
21
22impl PktLine {
23 pub fn encode(&self) -> Vec<u8> {
24 encode_pkt_line_payload(&self.0)
25 }
26
27 pub fn try_encode(&self) -> Result<Vec<u8>> {
28 validate_pkt_line_payload(&self.0)?;
29 Ok(self.encode())
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum PktLineFrame {
35 Data(Vec<u8>),
36 Flush,
37 Delimiter,
38 ResponseEnd,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct ProtocolErrorLine {
43 pub message: String,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum GitService {
48 UploadPack,
49 ReceivePack,
50 UploadArchive,
51}
52
53impl GitService {
54 pub fn as_str(self) -> &'static str {
55 match self {
56 Self::UploadPack => "git-upload-pack",
57 Self::ReceivePack => "git-receive-pack",
58 Self::UploadArchive => "git-upload-archive",
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct RefSpec {
65 pub force: bool,
66 pub negative: bool,
67 pub src: Option<String>,
68 pub dst: Option<String>,
69 pub pattern: bool,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct FetchHeadRecord {
74 pub oid: ObjectId,
75 pub not_for_merge: bool,
76 pub description: String,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct FetchRefUpdate {
81 pub src: String,
82 pub dst: Option<String>,
83 pub oid: ObjectId,
84 pub not_for_merge: bool,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct PushSourceRef {
89 pub name: String,
90 pub oid: ObjectId,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum SideBandChannel {
95 Data,
96 Progress,
97 Fatal,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct SideBandPacket {
102 pub channel: SideBandChannel,
103 pub data: Vec<u8>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Default)]
107pub struct SideBandDemux {
108 pub data: Vec<u8>,
109 pub progress: Vec<Vec<u8>>,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Default)]
113pub struct UploadArchiveRequest {
114 pub arguments: Vec<String>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum UploadArchiveResponse {
119 Ack { sideband: Vec<SideBandPacket> },
120 Nack { message: String },
121}
122
123impl PktLineFrame {
124 pub fn data(payload: impl Into<Vec<u8>>) -> Result<Self> {
125 let payload = payload.into();
126 validate_pkt_line_payload(&payload)?;
127 Ok(Self::Data(payload))
128 }
129
130 pub fn encode(&self) -> Vec<u8> {
131 match self {
132 Self::Data(payload) => encode_pkt_line_payload(payload),
133 Self::Flush => b"0000".to_vec(),
134 Self::Delimiter => b"0001".to_vec(),
135 Self::ResponseEnd => b"0002".to_vec(),
136 }
137 }
138
139 pub fn try_encode(&self) -> Result<Vec<u8>> {
140 match self {
141 Self::Data(payload) => try_encode_pkt_line_payload(payload),
142 Self::Flush | Self::Delimiter | Self::ResponseEnd => Ok(self.encode()),
143 }
144 }
145
146 pub fn parse(input: &[u8]) -> Result<(Self, usize)> {
147 if input.len() < 4 {
148 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
149 }
150 let len = parse_pkt_len(&input[..4])?;
151 match len {
152 0 => Ok((Self::Flush, 4)),
153 1 => Ok((Self::Delimiter, 4)),
154 2 => Ok((Self::ResponseEnd, 4)),
155 3 => Err(GitError::InvalidFormat(
156 "reserved pkt-line length 0003".into(),
157 )),
158 4..=PKT_LINE_MAX_LEN => {
159 if input.len() < len {
160 return Err(GitError::InvalidFormat(format!(
161 "truncated pkt-line payload: expected {} bytes, got {}",
162 len - 4,
163 input.len().saturating_sub(4)
164 )));
165 }
166 Ok((Self::Data(input[4..len].to_vec()), len))
167 }
168 _ => Err(GitError::InvalidFormat(format!(
169 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
170 ))),
171 }
172 }
173}
174
175fn validate_pkt_line_payload(payload: &[u8]) -> Result<()> {
176 if payload.len() > PKT_LINE_MAX_PAYLOAD_LEN {
177 return Err(GitError::InvalidFormat(format!(
178 "pkt-line payload exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
179 )));
180 }
181 Ok(())
182}
183
184fn pkt_line_header(len: usize) -> [u8; 4] {
185 const HEX: &[u8; 16] = b"0123456789abcdef";
186 [
187 HEX[(len >> 12) & 0xf],
188 HEX[(len >> 8) & 0xf],
189 HEX[(len >> 4) & 0xf],
190 HEX[len & 0xf],
191 ]
192}
193
194fn encode_pkt_line_payload(payload: &[u8]) -> Vec<u8> {
195 let len = payload.len() + 4;
196 let mut out = Vec::with_capacity(len);
197 out.extend_from_slice(&pkt_line_header(len));
198 out.extend_from_slice(payload);
199 out
200}
201
202fn try_encode_pkt_line_payload(payload: &[u8]) -> Result<Vec<u8>> {
203 validate_pkt_line_payload(payload)?;
204 Ok(encode_pkt_line_payload(payload))
205}
206
207pub fn parse_pkt_line_stream(mut input: &[u8]) -> Result<Vec<PktLineFrame>> {
208 let mut frames = Vec::new();
209 while !input.is_empty() {
210 let (frame, consumed) = PktLineFrame::parse(input)?;
211 frames.push(frame);
212 input = &input[consumed..];
213 }
214 Ok(frames)
215}
216
217fn parse_pkt_line_frames_until_flush_from(mut input: &[u8]) -> Result<(Vec<PktLineFrame>, usize)> {
218 let mut frames = Vec::new();
219 let mut total = 0usize;
220 loop {
221 if input.is_empty() {
222 return Err(GitError::InvalidFormat(
223 "pkt-line stream ended before flush".into(),
224 ));
225 }
226 let (frame, consumed) = PktLineFrame::parse(input)?;
227 total += consumed;
228 let done = matches!(frame, PktLineFrame::Flush);
229 frames.push(frame);
230 input = &input[consumed..];
231 if done {
232 return Ok((frames, total));
233 }
234 }
235}
236
237pub fn read_pkt_line_frame(reader: &mut impl Read) -> Result<Option<PktLineFrame>> {
238 let mut header = [0u8; 4];
239 let mut read = 0usize;
240 while read < header.len() {
241 match reader.read(&mut header[read..]) {
242 Ok(0) if read == 0 => return Ok(None),
243 Ok(0) => {
244 return Err(GitError::InvalidFormat("truncated pkt-line length".into()));
245 }
246 Ok(n) => read += n,
247 Err(err) if err.kind() == ErrorKind::Interrupted => {}
248 Err(err) => return Err(err.into()),
249 }
250 }
251
252 let len = parse_pkt_len(&header)?;
253 match len {
254 0 => Ok(Some(PktLineFrame::Flush)),
255 1 => Ok(Some(PktLineFrame::Delimiter)),
256 2 => Ok(Some(PktLineFrame::ResponseEnd)),
257 3 => Err(GitError::InvalidFormat(
258 "reserved pkt-line length 0003".into(),
259 )),
260 4..=PKT_LINE_MAX_LEN => {
261 let mut payload = vec![0; len - 4];
262 reader.read_exact(&mut payload)?;
263 Ok(Some(PktLineFrame::Data(payload)))
264 }
265 _ => Err(GitError::InvalidFormat(format!(
266 "pkt-line length exceeds {PKT_LINE_MAX_LEN}: {len}"
267 ))),
268 }
269}
270
271pub fn read_pkt_line_frames(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
272 let mut frames = Vec::new();
273 while let Some(frame) = read_pkt_line_frame(reader)? {
274 frames.push(frame);
275 }
276 Ok(frames)
277}
278
279pub fn read_pkt_line_frames_until_flush(reader: &mut impl Read) -> Result<Vec<PktLineFrame>> {
280 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::Flush))
281}
282
283pub fn read_pkt_line_frames_until_response_end(
284 reader: &mut impl Read,
285) -> Result<Vec<PktLineFrame>> {
286 read_pkt_line_frames_until_control(reader, |frame| matches!(frame, PktLineFrame::ResponseEnd))
287}
288
289fn read_pkt_line_frames_until_control(
290 reader: &mut impl Read,
291 stop: impl Fn(&PktLineFrame) -> bool,
292) -> Result<Vec<PktLineFrame>> {
293 let mut frames = Vec::new();
294 loop {
295 let Some(frame) = read_pkt_line_frame(reader)? else {
296 return Err(GitError::InvalidFormat(
297 "pkt-line stream ended before control packet".into(),
298 ));
299 };
300 let done = stop(&frame);
301 frames.push(frame);
302 if done {
303 return Ok(frames);
304 }
305 }
306}
307
308pub fn write_pkt_line_frame(writer: &mut impl Write, frame: &PktLineFrame) -> Result<()> {
309 match frame {
310 PktLineFrame::Data(payload) => write_pkt_line_payload(writer, payload)?,
311 PktLineFrame::Flush => writer.write_all(b"0000")?,
312 PktLineFrame::Delimiter => writer.write_all(b"0001")?,
313 PktLineFrame::ResponseEnd => writer.write_all(b"0002")?,
314 }
315 Ok(())
316}
317
318pub fn write_pkt_line_payload(writer: &mut impl Write, payload: &[u8]) -> Result<()> {
319 validate_pkt_line_payload(payload)?;
320 let len = payload.len() + 4;
321 writer.write_all(&pkt_line_header(len))?;
322 writer.write_all(payload)?;
323 Ok(())
324}
325
326pub fn write_pkt_line_frames(writer: &mut impl Write, frames: &[PktLineFrame]) -> Result<()> {
327 for frame in frames {
328 write_pkt_line_frame(writer, frame)?;
329 }
330 Ok(())
331}
332
333pub fn parse_error_line(payload: &[u8]) -> Result<ProtocolErrorLine> {
334 let text = parse_protocol_v2_line_text("protocol error line", payload)?;
335 let Some(message) = text.strip_prefix("ERR ") else {
336 return Err(GitError::InvalidFormat(
337 "protocol error line must start with ERR".into(),
338 ));
339 };
340 validate_protocol_error_message(message)?;
341 Ok(ProtocolErrorLine {
342 message: message.to_string(),
343 })
344}
345
346pub fn encode_error_line(error: &ProtocolErrorLine) -> Result<Vec<u8>> {
347 validate_protocol_error_message(&error.message)?;
348 Ok(line_from_str(&format!("ERR {}", error.message)))
349}
350
351pub fn parse_error_frame(frame: &PktLineFrame) -> Result<Option<ProtocolErrorLine>> {
352 match frame {
353 PktLineFrame::Data(payload) if trim_trailing_lf(payload).starts_with(b"ERR ") => {
354 parse_error_line(payload).map(Some)
355 }
356 PktLineFrame::Data(_)
357 | PktLineFrame::Flush
358 | PktLineFrame::Delimiter
359 | PktLineFrame::ResponseEnd => Ok(None),
360 }
361}
362
363pub fn read_error_line(reader: &mut impl Read) -> Result<ProtocolErrorLine> {
364 let Some(frame) = read_pkt_line_frame(reader)? else {
365 return Err(GitError::InvalidFormat(
366 "pkt-line stream ended before protocol error line".into(),
367 ));
368 };
369 match frame {
370 PktLineFrame::Data(payload) => parse_error_line(&payload),
371 _ => Err(GitError::InvalidFormat(
372 "protocol error line must be a data packet".into(),
373 )),
374 }
375}
376
377pub fn write_error_line(writer: &mut impl Write, error: &ProtocolErrorLine) -> Result<()> {
378 write_pkt_line_frame(writer, &PktLineFrame::data(encode_error_line(error)?)?)
379}
380
381pub fn parse_git_service(value: &str) -> Result<GitService> {
382 match value {
383 "git-upload-pack" => Ok(GitService::UploadPack),
384 "git-receive-pack" => Ok(GitService::ReceivePack),
385 "git-upload-archive" => Ok(GitService::UploadArchive),
386 other => Err(GitError::InvalidFormat(format!(
387 "unsupported git service {other}"
388 ))),
389 }
390}
391
392pub fn parse_refspec(value: &str) -> Result<RefSpec> {
393 validate_refspec_value(value)?;
394 let (force, value) = value
395 .strip_prefix('+')
396 .map_or((false, value), |value| (true, value));
397 let (negative, value) = value
398 .strip_prefix('^')
399 .map_or((false, value), |value| (true, value));
400 if force && negative {
401 return Err(GitError::InvalidFormat(
402 "negative refspec must not be forced".into(),
403 ));
404 }
405 let (src, dst) = if negative {
406 if value.contains(':') {
407 return Err(GitError::InvalidFormat(
408 "negative refspec must not have a destination".into(),
409 ));
410 }
411 (Some(value), None)
412 } else if let Some((src, dst)) = value.split_once(':') {
413 (non_empty(src), non_empty(dst))
414 } else {
415 (Some(value), None)
416 };
417 if src.is_none() && dst.is_none() && value != ":" {
418 return Err(GitError::InvalidFormat(
419 "refspec must include a source or destination".into(),
420 ));
421 }
422 if negative && src.is_none() {
423 return Err(GitError::InvalidFormat(
424 "negative refspec is missing a source".into(),
425 ));
426 }
427 if let Some(src) = src {
428 validate_refspec_endpoint("refspec source", src)?;
429 }
430 if let Some(dst) = dst {
431 validate_refspec_endpoint("refspec destination", dst)?;
432 }
433 let src_pattern_count = src.map(count_refspec_wildcards).unwrap_or(0);
434 let dst_pattern_count = dst.map(count_refspec_wildcards).unwrap_or(0);
435 if src_pattern_count > 1 || dst_pattern_count > 1 {
436 return Err(GitError::InvalidFormat(
437 "refspec endpoint has too many wildcards".into(),
438 ));
439 }
440 if dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
441 return Err(GitError::InvalidFormat(
442 "refspec wildcard must appear in both source and destination".into(),
443 ));
444 }
445 Ok(RefSpec {
446 force,
447 negative,
448 src: src.map(str::to_string),
449 dst: dst.map(str::to_string),
450 pattern: src_pattern_count == 1 || dst_pattern_count == 1,
451 })
452}
453
454pub fn encode_refspec(refspec: &RefSpec) -> Result<String> {
455 validate_refspec_shape(refspec)?;
456 let mut out = String::new();
457 if refspec.force {
458 out.push('+');
459 }
460 if refspec.negative {
461 out.push('^');
462 }
463 if let Some(src) = &refspec.src {
464 out.push_str(src);
465 }
466 if !refspec.negative && refspec.src.is_none() && refspec.dst.is_none() {
467 out.push(':');
468 } else if !refspec.negative && refspec.dst.is_some() {
469 out.push(':');
470 if let Some(dst) = &refspec.dst {
471 out.push_str(dst);
472 }
473 }
474 Ok(out)
475}
476
477pub fn refspec_matches_source(refspec: &RefSpec, source: &str) -> Result<bool> {
478 Ok(refspec_map_source(refspec, source)?.is_some())
479}
480
481pub fn refspec_map_source(refspec: &RefSpec, source: &str) -> Result<Option<String>> {
482 validate_refspec_shape(refspec)?;
483 validate_refspec_endpoint("refspec match source", source)?;
484 let Some(src) = refspec.src.as_deref() else {
485 return Ok(None);
486 };
487 if refspec.pattern {
488 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
489 return Ok(None);
490 };
491 let Some(middle) = source
492 .strip_prefix(src_prefix)
493 .and_then(|value| value.strip_suffix(src_suffix))
494 else {
495 return Ok(None);
496 };
497 if let Some(dst) = refspec.dst.as_deref() {
498 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
499 GitError::InvalidFormat("pattern refspec destination is missing wildcard".into())
500 })?;
501 return Ok(Some(format!("{dst_prefix}{middle}{dst_suffix}")));
502 }
503 return Ok(Some(source.to_string()));
504 }
505 if src == source {
506 return Ok(Some(
507 refspec.dst.clone().unwrap_or_else(|| source.to_string()),
508 ));
509 }
510 Ok(None)
511}
512
513pub fn fetch_head_ref_description(refname: &str) -> Result<String> {
514 validate_fetch_head_description_field(refname)?;
515 if let Some(branch) = refname.strip_prefix("refs/heads/") {
516 Ok(format!("branch '{branch}'"))
517 } else if let Some(tag) = refname.strip_prefix("refs/tags/") {
518 Ok(format!("tag '{tag}'"))
519 } else {
520 Ok(refname.to_string())
521 }
522}
523
524pub fn fetch_head_remote_description(refname: &str, remote: &str) -> Result<String> {
525 validate_fetch_head_description_field(remote)?;
526 Ok(format!(
527 "{} of {remote}",
528 fetch_head_ref_description(refname)?
529 ))
530}
531
532pub fn parse_fetch_head(format: ObjectFormat, input: &[u8]) -> Result<Vec<FetchHeadRecord>> {
533 if input.is_empty() {
534 return Ok(Vec::new());
535 }
536 input
537 .split_inclusive(|byte| *byte == b'\n')
538 .map(|line| parse_fetch_head_record(format, line))
539 .collect()
540}
541
542pub fn encode_fetch_head(records: &[FetchHeadRecord]) -> Result<Vec<u8>> {
543 let mut out = Vec::new();
544 for record in records {
545 validate_fetch_head_description_field(&record.description)?;
546 out.extend_from_slice(record.oid.to_string().as_bytes());
547 out.push(b'\t');
548 if record.not_for_merge {
549 out.extend_from_slice(b"not-for-merge");
550 }
551 out.push(b'\t');
552 out.extend_from_slice(record.description.as_bytes());
553 out.push(b'\n');
554 }
555 Ok(out)
556}
557
558pub fn read_fetch_head(
559 format: ObjectFormat,
560 reader: &mut impl Read,
561) -> Result<Vec<FetchHeadRecord>> {
562 let mut input = Vec::new();
563 reader.read_to_end(&mut input)?;
564 parse_fetch_head(format, &input)
565}
566
567pub fn write_fetch_head(writer: &mut impl Write, records: &[FetchHeadRecord]) -> Result<()> {
568 for record in records {
569 validate_fetch_head_description_field(&record.description)?;
570 writer.write_all(record.oid.to_string().as_bytes())?;
571 writer.write_all(b"\t")?;
572 if record.not_for_merge {
573 writer.write_all(b"not-for-merge")?;
574 }
575 writer.write_all(b"\t")?;
576 writer.write_all(record.description.as_bytes())?;
577 writer.write_all(b"\n")?;
578 }
579 Ok(())
580}
581
582fn find_advertised_ref_by_name_abbrev<'a>(
588 refs: &'a [RefAdvertisement],
589 name: &str,
590) -> Option<&'a RefAdvertisement> {
591 let mut best: Option<(&RefAdvertisement, usize)> = None;
592 for reference in refs {
593 let score = fetch_refname_match_score(name, &reference.name);
594 if score > best.map(|(_, score)| score).unwrap_or(0) {
595 best = Some((reference, score));
596 }
597 }
598 best.map(|(reference, _)| reference)
599}
600
601fn fetch_refname_match_score(abbrev: &str, full: &str) -> usize {
604 let expansions = [
605 abbrev.to_string(),
606 format!("refs/{abbrev}"),
607 format!("refs/tags/{abbrev}"),
608 format!("refs/heads/{abbrev}"),
609 format!("refs/remotes/{abbrev}"),
610 format!("refs/remotes/{abbrev}/HEAD"),
611 ];
612 for (index, candidate) in expansions.iter().enumerate() {
613 if candidate == full {
614 return expansions.len() - index;
615 }
616 }
617 0
618}
619
620fn fetch_local_ref_name(name: &str) -> String {
624 if name.starts_with("refs/") {
625 name.to_string()
626 } else if name.starts_with("heads/")
627 || name.starts_with("tags/")
628 || name.starts_with("remotes/")
629 {
630 format!("refs/{name}")
631 } else {
632 format!("refs/heads/{name}")
633 }
634}
635
636pub fn plan_fetch_ref_updates(
637 refs: &[RefAdvertisement],
638 refspecs: &[RefSpec],
639 auto_follow_tags: bool,
640) -> Result<Vec<FetchRefUpdate>> {
641 let negative = refspecs
642 .iter()
643 .filter(|refspec| refspec.negative)
644 .collect::<Vec<_>>();
645 let mut updates = Vec::new();
646 for refspec in refspecs.iter().filter(|refspec| !refspec.negative) {
647 validate_refspec_shape(refspec)?;
648 let Some(src) = refspec.src.as_deref() else {
649 return Err(GitError::InvalidFormat(
650 "fetch refspec is missing a source".into(),
651 ));
652 };
653 if refspec.pattern {
654 for reference in refs {
655 if refspec_is_excluded(&negative, &reference.name)? {
656 continue;
657 }
658 if let Some(dst) = refspec_map_source(refspec, &reference.name)? {
659 updates.push(FetchRefUpdate {
660 src: reference.name.clone(),
661 dst: Some(dst),
662 oid: reference.oid,
663 not_for_merge: false,
664 });
665 }
666 }
667 continue;
668 }
669 if refspec_is_excluded(&negative, src)? {
670 continue;
671 }
672 let Some(reference) = find_advertised_ref_by_name_abbrev(refs, src) else {
673 return Err(GitError::reference_not_found(format!("remote ref {src}")));
674 };
675 updates.push(FetchRefUpdate {
676 src: reference.name.clone(),
677 dst: refspec.dst.as_deref().map(fetch_local_ref_name),
678 oid: reference.oid,
679 not_for_merge: false,
680 });
681 }
682 if auto_follow_tags && updates.iter().any(|update| update.dst.is_some()) {
683 let fetched_oids = updates.iter().map(|update| update.oid).collect::<Vec<_>>();
684 let fetched_srcs = updates
685 .iter()
686 .map(|update| update.src.clone())
687 .collect::<Vec<_>>();
688 for reference in refs {
689 if reference.name.starts_with("refs/tags/")
690 && fetched_oids.iter().any(|oid| oid == &reference.oid)
691 && !fetched_srcs.contains(&reference.name)
692 && !refspec_is_excluded(&negative, &reference.name)?
693 {
694 updates.push(FetchRefUpdate {
695 src: reference.name.clone(),
696 dst: Some(reference.name.clone()),
697 oid: reference.oid,
698 not_for_merge: true,
699 });
700 }
701 }
702 }
703 Ok(updates)
704}
705
706pub fn fetch_ref_updates_to_fetch_head(
707 updates: &[FetchRefUpdate],
708 remote: &str,
709) -> Result<Vec<FetchHeadRecord>> {
710 updates
711 .iter()
712 .map(|update| {
713 Ok(FetchHeadRecord {
714 oid: update.oid,
715 not_for_merge: update.not_for_merge,
716 description: fetch_head_remote_description(&update.src, remote)?,
717 })
718 })
719 .collect()
720}
721
722pub fn plan_push_commands(
723 format: ObjectFormat,
724 local_refs: &[PushSourceRef],
725 remote_refs: &[RefAdvertisement],
726 refspecs: &[RefSpec],
727) -> Result<Vec<ReceivePackCommand>> {
728 let zero = zero_object_id(format)?;
729 let mut commands = Vec::new();
730 for refspec in refspecs {
731 validate_refspec_shape(refspec)?;
732 if refspec.negative {
733 return Err(GitError::InvalidFormat(
734 "push refspec must not be negative".into(),
735 ));
736 }
737 match (refspec.src.as_deref(), refspec.dst.as_deref()) {
738 (None, None) => {
739 for local in local_refs {
740 validate_push_source_ref(format, local)?;
741 if let Some(remote) = remote_ref(remote_refs, &local.name) {
742 commands.push(ReceivePackCommand {
743 old_id: remote.oid,
744 new_id: local.oid,
745 name: local.name.clone(),
746 });
747 }
748 }
749 }
750 (None, Some(dst)) => {
751 validate_refspec_endpoint("push destination", dst)?;
752 let remote = remote_ref(remote_refs, dst)
753 .ok_or_else(|| GitError::reference_not_found(format!("remote ref {dst}")))?;
754 commands.push(ReceivePackCommand {
755 old_id: remote.oid,
756 new_id: zero.clone(),
757 name: dst.to_string(),
758 });
759 }
760 (Some(src), dst) if refspec.pattern => {
761 let Some((src_prefix, src_suffix)) = src.split_once('*') else {
762 return Err(GitError::InvalidFormat(
763 "pattern push refspec source is missing wildcard".into(),
764 ));
765 };
766 let dst = dst.ok_or_else(|| {
767 GitError::InvalidFormat("pattern push refspec is missing destination".into())
768 })?;
769 let (dst_prefix, dst_suffix) = dst.split_once('*').ok_or_else(|| {
770 GitError::InvalidFormat(
771 "pattern push refspec destination is missing wildcard".into(),
772 )
773 })?;
774 for local in local_refs {
775 validate_push_source_ref(format, local)?;
776 let Some(middle) = local
777 .name
778 .strip_prefix(src_prefix)
779 .and_then(|value| value.strip_suffix(src_suffix))
780 else {
781 continue;
782 };
783 let name = format!("{dst_prefix}{middle}{dst_suffix}");
784 let old_id = remote_ref(remote_refs, &name)
785 .map(|reference| reference.oid)
786 .unwrap_or_else(|| zero.clone());
787 commands.push(ReceivePackCommand {
788 old_id,
789 new_id: local.oid,
790 name,
791 });
792 }
793 }
794 (Some(src), dst) => {
795 validate_refspec_endpoint("push source", src)?;
796 let local = local_ref(local_refs, src)
797 .ok_or_else(|| GitError::reference_not_found(format!("local ref {src}")))?;
798 validate_push_source_ref(format, local)?;
799 let name = dst.unwrap_or(src);
800 validate_refspec_endpoint("push destination", name)?;
801 let old_id = remote_ref(remote_refs, name)
802 .map(|reference| reference.oid)
803 .unwrap_or_else(|| zero.clone());
804 commands.push(ReceivePackCommand {
805 old_id,
806 new_id: local.oid,
807 name: name.to_string(),
808 });
809 }
810 }
811 }
812 Ok(commands)
813}
814
815pub fn build_receive_pack_push_request(
816 features: &ReceivePackFeatures,
817 commands: Vec<ReceivePackCommand>,
818 packfile: Vec<u8>,
819 options: ReceivePackPushRequestOptions,
820) -> Result<ReceivePackPushRequest> {
821 let mut capabilities = Vec::new();
822 if options.report_status_v2 {
823 require_receive_pack_feature(features.report_status_v2, "report-status-v2")?;
824 capabilities.push(Capability {
825 name: "report-status-v2".into(),
826 value: None,
827 });
828 } else if options.report_status {
829 require_receive_pack_feature(features.report_status, "report-status")?;
830 capabilities.push(Capability {
831 name: "report-status".into(),
832 value: None,
833 });
834 }
835 if commands.iter().any(is_receive_pack_delete_command) {
836 require_receive_pack_feature(features.delete_refs, "delete-refs")?;
837 capabilities.push(Capability {
838 name: "delete-refs".into(),
839 value: None,
840 });
841 }
842 if options.atomic {
843 require_receive_pack_feature(features.atomic, "atomic")?;
844 capabilities.push(Capability {
845 name: "atomic".into(),
846 value: None,
847 });
848 }
849 if options.ofs_delta {
850 require_receive_pack_feature(features.ofs_delta, "ofs-delta")?;
851 capabilities.push(Capability {
852 name: "ofs-delta".into(),
853 value: None,
854 });
855 }
856 if options.side_band_64k {
857 require_receive_pack_feature(features.side_band_64k, "side-band-64k")?;
858 capabilities.push(Capability {
859 name: "side-band-64k".into(),
860 value: None,
861 });
862 }
863 if options.quiet {
864 require_receive_pack_feature(features.quiet, "quiet")?;
865 capabilities.push(Capability {
866 name: "quiet".into(),
867 value: None,
868 });
869 }
870 if let Some(agent) = &options.agent {
871 validate_capability_field("receive-pack request agent", agent)?;
872 capabilities.push(Capability {
873 name: "agent".into(),
874 value: Some(agent.clone()),
875 });
876 }
877 if let Some(format) = options.object_format {
878 if features.object_format != Some(format) {
879 return Err(GitError::InvalidFormat(
880 "receive-pack request object-format was not advertised".into(),
881 ));
882 }
883 capabilities.push(Capability {
884 name: "object-format".into(),
885 value: Some(format.name().into()),
886 });
887 }
888 let push_options = if options.push_options.is_empty() {
889 None
890 } else {
891 require_receive_pack_feature(features.push_options, "push-options")?;
892 for option in &options.push_options {
893 validate_receive_pack_push_option(option.as_bytes())?;
894 }
895 capabilities.push(Capability {
896 name: "push-options".into(),
897 value: None,
898 });
899 Some(options.push_options)
900 };
901 let request = ReceivePackPushRequest {
902 commands: ReceivePackRequest {
903 commands,
904 capabilities,
905 shallow: Vec::new(),
906 },
907 push_options,
908 packfile,
909 };
910 validate_receive_pack_push_request_features(features, &request)?;
911 Ok(request)
912}
913
914pub fn smart_http_info_refs_path(repository_path: &str, service: GitService) -> Result<String> {
915 validate_smart_http_service(service)?;
916 let repository_path = normalize_http_repository_path(repository_path)?;
917 Ok(format!(
918 "{repository_path}/info/refs?service={}",
919 service.as_str()
920 ))
921}
922
923pub fn smart_http_rpc_path(repository_path: &str, service: GitService) -> Result<String> {
924 validate_smart_http_service(service)?;
925 let repository_path = normalize_http_repository_path(repository_path)?;
926 Ok(format!("{repository_path}/{}", service.as_str()))
927}
928
929pub fn dumb_http_info_refs_path(repository_path: &str) -> Result<String> {
930 let repository_path = normalize_http_repository_path(repository_path)?;
931 Ok(format!("{repository_path}/info/refs"))
932}
933
934pub fn dumb_http_alternates_path(repository_path: &str) -> Result<String> {
935 let repository_path = normalize_http_repository_path(repository_path)?;
936 Ok(format!("{repository_path}/objects/info/http-alternates"))
937}
938
939pub fn dumb_http_packs_path(repository_path: &str) -> Result<String> {
940 let repository_path = normalize_http_repository_path(repository_path)?;
941 Ok(format!("{repository_path}/objects/info/packs"))
942}
943
944pub fn dumb_http_loose_object_path(repository_path: &str, oid: &ObjectId) -> Result<String> {
945 let repository_path = normalize_http_repository_path(repository_path)?;
946 let oid = oid.to_string();
947 let (directory, file) = oid.split_at(2);
948 Ok(format!("{repository_path}/objects/{directory}/{file}"))
949}
950
951pub fn dumb_http_pack_file_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
952 dumb_http_pack_resource_path(repository_path, hash, "pack")
953}
954
955pub fn dumb_http_pack_index_path(repository_path: &str, hash: &ObjectId) -> Result<String> {
956 dumb_http_pack_resource_path(repository_path, hash, "idx")
957}
958
959pub fn smart_http_advertisement_content_type(service: GitService) -> Result<String> {
960 validate_smart_http_service(service)?;
961 Ok(format!("application/x-{}-advertisement", service.as_str()))
962}
963
964pub fn smart_http_rpc_request_content_type(service: GitService) -> Result<String> {
965 validate_smart_http_service(service)?;
966 Ok(format!("application/x-{}-request", service.as_str()))
967}
968
969pub fn smart_http_rpc_result_content_type(service: GitService) -> Result<String> {
970 validate_smart_http_service(service)?;
971 Ok(format!("application/x-{}-result", service.as_str()))
972}
973
974pub fn parse_smart_http_advertisement_content_type(value: &str) -> Result<GitService> {
975 parse_smart_http_content_type(value, "-advertisement")
976}
977
978pub fn parse_smart_http_rpc_request_content_type(value: &str) -> Result<GitService> {
979 parse_smart_http_content_type(value, "-request")
980}
981
982pub fn parse_smart_http_rpc_result_content_type(value: &str) -> Result<GitService> {
983 parse_smart_http_content_type(value, "-result")
984}
985
986pub fn parse_sideband_packet(payload: &[u8]) -> Result<SideBandPacket> {
987 let Some((&channel, data)) = payload.split_first() else {
988 return Err(GitError::InvalidFormat("sideband packet is empty".into()));
989 };
990 let channel = match channel {
991 1 => SideBandChannel::Data,
992 2 => SideBandChannel::Progress,
993 3 => SideBandChannel::Fatal,
994 other => {
995 return Err(GitError::InvalidFormat(format!(
996 "invalid sideband channel {other}"
997 )));
998 }
999 };
1000 Ok(SideBandPacket {
1001 channel,
1002 data: data.to_vec(),
1003 })
1004}
1005
1006pub fn encode_sideband_packet(packet: &SideBandPacket) -> Result<Vec<u8>> {
1007 let mut out = Vec::with_capacity(packet.data.len() + 1);
1008 out.push(match packet.channel {
1009 SideBandChannel::Data => 1,
1010 SideBandChannel::Progress => 2,
1011 SideBandChannel::Fatal => 3,
1012 });
1013 out.extend_from_slice(&packet.data);
1014 if out.len() > PKT_LINE_MAX_PAYLOAD_LEN {
1015 return Err(GitError::InvalidFormat(format!(
1016 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1017 )));
1018 }
1019 Ok(out)
1020}
1021
1022pub fn write_sideband_packet(writer: &mut impl Write, packet: &SideBandPacket) -> Result<()> {
1023 write_sideband_payload(writer, packet.channel, &packet.data)
1024}
1025
1026fn write_sideband_payload(
1027 writer: &mut impl Write,
1028 channel: SideBandChannel,
1029 data: &[u8],
1030) -> Result<()> {
1031 let payload_len = data
1032 .len()
1033 .checked_add(1)
1034 .ok_or_else(|| GitError::InvalidFormat("sideband packet length overflow".into()))?;
1035 if payload_len > PKT_LINE_MAX_PAYLOAD_LEN {
1036 return Err(GitError::InvalidFormat(format!(
1037 "sideband packet exceeds {PKT_LINE_MAX_PAYLOAD_LEN} bytes"
1038 )));
1039 }
1040 writer.write_all(&pkt_line_header(payload_len + 4))?;
1041 writer.write_all(&[match channel {
1042 SideBandChannel::Data => 1,
1043 SideBandChannel::Progress => 2,
1044 SideBandChannel::Fatal => 3,
1045 }])?;
1046 writer.write_all(data)?;
1047 Ok(())
1048}
1049
1050pub fn parse_sideband_packets(payloads: &[Vec<u8>]) -> Result<Vec<SideBandPacket>> {
1051 payloads
1052 .iter()
1053 .map(|payload| parse_sideband_packet(payload))
1054 .collect()
1055}
1056
1057pub fn encode_sideband_packets(packets: &[SideBandPacket]) -> Result<Vec<Vec<u8>>> {
1058 packets.iter().map(encode_sideband_packet).collect()
1059}
1060
1061pub fn parse_sideband_stream(frames: &[PktLineFrame]) -> Result<Vec<SideBandPacket>> {
1062 let mut packets = Vec::new();
1063 let mut saw_flush = false;
1064 for (idx, frame) in frames.iter().enumerate() {
1065 match frame {
1066 PktLineFrame::Data(payload) if !saw_flush => {
1067 packets.push(parse_sideband_packet(payload)?);
1068 }
1069 PktLineFrame::Data(_) => {
1070 return Err(GitError::InvalidFormat(
1071 "sideband stream has data after flush".into(),
1072 ));
1073 }
1074 PktLineFrame::Flush => {
1075 saw_flush = true;
1076 if idx + 1 != frames.len() {
1077 return Err(GitError::InvalidFormat(
1078 "sideband stream has frames after flush".into(),
1079 ));
1080 }
1081 }
1082 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1083 return Err(GitError::InvalidFormat(
1084 "sideband stream contains a non-flush control packet".into(),
1085 ));
1086 }
1087 }
1088 }
1089 if !saw_flush {
1090 return Err(GitError::InvalidFormat(
1091 "sideband stream missing flush".into(),
1092 ));
1093 }
1094 Ok(packets)
1095}
1096
1097pub fn encode_sideband_stream(packets: &[SideBandPacket]) -> Result<Vec<PktLineFrame>> {
1098 let mut frames = Vec::new();
1099 for packet in packets {
1100 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
1101 }
1102 frames.push(PktLineFrame::Flush);
1103 Ok(frames)
1104}
1105
1106pub fn read_sideband_stream(reader: &mut impl Read) -> Result<Vec<SideBandPacket>> {
1107 let frames = read_pkt_line_frames_until_flush(reader)?;
1108 parse_sideband_stream(&frames)
1109}
1110
1111pub fn write_sideband_stream(writer: &mut impl Write, packets: &[SideBandPacket]) -> Result<()> {
1112 for packet in packets {
1113 write_sideband_packet(writer, packet)?;
1114 }
1115 writer.write_all(b"0000")?;
1116 Ok(())
1117}
1118
1119pub fn demux_sideband_packets(packets: &[SideBandPacket]) -> Result<SideBandDemux> {
1120 let mut out = SideBandDemux::default();
1121 for packet in packets {
1122 match packet.channel {
1123 SideBandChannel::Data => out.data.extend_from_slice(&packet.data),
1124 SideBandChannel::Progress => out.progress.push(packet.data.clone()),
1125 SideBandChannel::Fatal => {
1126 let message = String::from_utf8_lossy(&packet.data).into_owned();
1127 return Err(GitError::InvalidFormat(format!(
1128 "sideband fatal: {message}"
1129 )));
1130 }
1131 }
1132 }
1133 Ok(out)
1134}
1135
1136pub fn parse_and_demux_sideband_packets(payloads: &[Vec<u8>]) -> Result<SideBandDemux> {
1137 let packets = parse_sideband_packets(payloads)?;
1138 demux_sideband_packets(&packets)
1139}
1140
1141pub fn demux_sideband_stream(frames: &[PktLineFrame]) -> Result<SideBandDemux> {
1142 let packets = parse_sideband_stream(frames)?;
1143 demux_sideband_packets(&packets)
1144}
1145
1146pub fn read_and_demux_sideband_stream(reader: &mut impl Read) -> Result<SideBandDemux> {
1147 let packets = read_sideband_stream(reader)?;
1148 demux_sideband_packets(&packets)
1149}
1150
1151pub fn parse_upload_archive_request(frames: &[PktLineFrame]) -> Result<UploadArchiveRequest> {
1152 let mut request = UploadArchiveRequest::default();
1153 let mut saw_flush = false;
1154 for (idx, frame) in frames.iter().enumerate() {
1155 match frame {
1156 PktLineFrame::Data(payload) if !saw_flush => {
1157 let text = parse_protocol_v2_line_text("upload-archive request argument", payload)?;
1158 let argument = text.strip_prefix("argument ").ok_or_else(|| {
1159 GitError::InvalidFormat("upload-archive request line must be argument".into())
1160 })?;
1161 validate_upload_archive_argument(argument)?;
1162 request.arguments.push(argument.to_string());
1163 }
1164 PktLineFrame::Data(_) => {
1165 return Err(GitError::InvalidFormat(
1166 "upload-archive request has data after flush".into(),
1167 ));
1168 }
1169 PktLineFrame::Flush => {
1170 saw_flush = true;
1171 if idx + 1 != frames.len() {
1172 return Err(GitError::InvalidFormat(
1173 "upload-archive request has frames after flush".into(),
1174 ));
1175 }
1176 }
1177 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
1178 return Err(GitError::InvalidFormat(
1179 "upload-archive request contains a non-flush control packet".into(),
1180 ));
1181 }
1182 }
1183 }
1184 if !saw_flush {
1185 return Err(GitError::InvalidFormat(
1186 "upload-archive request missing flush".into(),
1187 ));
1188 }
1189 if request.arguments.is_empty() {
1190 return Err(GitError::InvalidFormat(
1191 "upload-archive request is missing arguments".into(),
1192 ));
1193 }
1194 Ok(request)
1195}
1196
1197pub fn encode_upload_archive_request(request: &UploadArchiveRequest) -> Result<Vec<PktLineFrame>> {
1198 if request.arguments.is_empty() {
1199 return Err(GitError::InvalidFormat(
1200 "upload-archive request is missing arguments".into(),
1201 ));
1202 }
1203 let mut frames = Vec::new();
1204 for argument in &request.arguments {
1205 validate_upload_archive_argument(argument)?;
1206 frames.push(PktLineFrame::data(line_from_str(&format!(
1207 "argument {argument}"
1208 )))?);
1209 }
1210 frames.push(PktLineFrame::Flush);
1211 Ok(frames)
1212}
1213
1214pub fn read_upload_archive_request(reader: &mut impl Read) -> Result<UploadArchiveRequest> {
1215 let frames = read_pkt_line_frames_until_flush(reader)?;
1216 parse_upload_archive_request(&frames)
1217}
1218
1219pub fn write_upload_archive_request(
1220 writer: &mut impl Write,
1221 request: &UploadArchiveRequest,
1222) -> Result<()> {
1223 if request.arguments.is_empty() {
1224 return Err(GitError::InvalidFormat(
1225 "upload-archive request is missing arguments".into(),
1226 ));
1227 }
1228 for argument in &request.arguments {
1229 validate_upload_archive_argument(argument)?;
1230 write_pkt_line_payload(writer, &line_from_str(&format!("argument {argument}")))?;
1231 }
1232 writer.write_all(b"0000")?;
1233 Ok(())
1234}
1235
1236pub fn parse_upload_archive_response(frames: &[PktLineFrame]) -> Result<UploadArchiveResponse> {
1237 let Some((first, rest)) = frames.split_first() else {
1238 return Err(GitError::InvalidFormat(
1239 "upload-archive response is empty".into(),
1240 ));
1241 };
1242 let PktLineFrame::Data(payload) = first else {
1243 return Err(GitError::InvalidFormat(
1244 "upload-archive response must start with a data packet".into(),
1245 ));
1246 };
1247 let text = parse_protocol_v2_line_text("upload-archive response status", payload)?;
1248 if text == "ACK" {
1249 return Ok(UploadArchiveResponse::Ack {
1250 sideband: parse_sideband_stream(rest)?,
1251 });
1252 }
1253 if let Some(message) = text.strip_prefix("NACK ") {
1254 validate_upload_archive_status_message(message)?;
1255 if !matches!(rest, [PktLineFrame::Flush]) {
1256 return Err(GitError::InvalidFormat(
1257 "upload-archive NACK response must end with flush".into(),
1258 ));
1259 }
1260 return Ok(UploadArchiveResponse::Nack {
1261 message: message.to_string(),
1262 });
1263 }
1264 Err(GitError::InvalidFormat(format!(
1265 "unsupported upload-archive response status {text}"
1266 )))
1267}
1268
1269pub fn encode_upload_archive_response(
1270 response: &UploadArchiveResponse,
1271) -> Result<Vec<PktLineFrame>> {
1272 let mut frames = Vec::new();
1273 match response {
1274 UploadArchiveResponse::Ack { sideband } => {
1275 frames.push(PktLineFrame::data(line_from_str("ACK"))?);
1276 frames.extend(encode_sideband_stream(sideband)?);
1277 }
1278 UploadArchiveResponse::Nack { message } => {
1279 validate_upload_archive_status_message(message)?;
1280 frames.push(PktLineFrame::data(line_from_str(&format!(
1281 "NACK {message}"
1282 )))?);
1283 frames.push(PktLineFrame::Flush);
1284 }
1285 }
1286 Ok(frames)
1287}
1288
1289pub fn read_upload_archive_response(reader: &mut impl Read) -> Result<UploadArchiveResponse> {
1290 let frames = read_pkt_line_frames_until_flush(reader)?;
1291 parse_upload_archive_response(&frames)
1292}
1293
1294pub fn write_upload_archive_response(
1295 writer: &mut impl Write,
1296 response: &UploadArchiveResponse,
1297) -> Result<()> {
1298 match response {
1299 UploadArchiveResponse::Ack { sideband } => {
1300 write_pkt_line_payload(writer, b"ACK\n")?;
1301 write_sideband_stream(writer, sideband)?;
1302 }
1303 UploadArchiveResponse::Nack { message } => {
1304 validate_upload_archive_status_message(message)?;
1305 write_pkt_line_payload(writer, &line_from_str(&format!("NACK {message}")))?;
1306 writer.write_all(b"0000")?;
1307 }
1308 }
1309 Ok(())
1310}
1311
1312pub fn demux_upload_archive_response(response: &UploadArchiveResponse) -> Result<SideBandDemux> {
1313 match response {
1314 UploadArchiveResponse::Ack { sideband } => demux_sideband_packets(sideband),
1315 UploadArchiveResponse::Nack { message } => Err(GitError::InvalidFormat(format!(
1316 "upload-archive NACK: {message}"
1317 ))),
1318 }
1319}
1320
1321fn parse_pkt_len(bytes: &[u8]) -> Result<usize> {
1322 let mut len = 0usize;
1323 for byte in bytes {
1324 len = (len << 4) | hex_nibble(*byte)? as usize;
1325 }
1326 Ok(len)
1327}
1328
1329fn hex_nibble(byte: u8) -> Result<u8> {
1330 match byte {
1331 b'0'..=b'9' => Ok(byte - b'0'),
1332 b'a'..=b'f' => Ok(byte - b'a' + 10),
1333 b'A'..=b'F' => Ok(byte - b'A' + 10),
1334 _ => Err(GitError::InvalidFormat(format!(
1335 "invalid pkt-line length byte {byte:#04x}"
1336 ))),
1337 }
1338}
1339
1340#[derive(Debug, Clone, PartialEq, Eq)]
1341pub struct TransportHandshake {
1342 pub protocol: ProtocolVersion,
1343 pub capabilities: Vec<Capability>,
1344}
1345
1346#[derive(Debug, Clone, PartialEq, Eq)]
1347pub struct RefAdvertisement {
1348 pub oid: ObjectId,
1349 pub name: String,
1350 pub capabilities: Vec<Capability>,
1351}
1352
1353#[derive(Debug, Clone, PartialEq, Eq)]
1354pub struct DumbHttpRefRecord {
1355 pub oid: ObjectId,
1356 pub name: String,
1357 pub peeled: bool,
1358}
1359
1360#[derive(Debug, Clone, PartialEq, Eq)]
1361pub struct DumbHttpPackRecord {
1362 pub hash: ObjectId,
1363}
1364
1365#[derive(Debug, Clone, PartialEq, Eq)]
1366pub struct RefAdvertisementSet {
1367 pub protocol: ProtocolVersion,
1368 pub refs: Vec<RefAdvertisement>,
1369 pub shallow: Vec<ObjectId>,
1370}
1371
1372#[derive(Debug, Clone, PartialEq, Eq, Default)]
1373pub struct UploadPackRequest {
1374 pub wants: Vec<ObjectId>,
1375 pub capabilities: Vec<Capability>,
1376 pub shallow: Vec<ObjectId>,
1377 pub deepen: Option<u32>,
1378 pub deepen_since: Option<u64>,
1379 pub deepen_not: Vec<String>,
1380 pub filter: Option<String>,
1381}
1382
1383#[derive(Debug, Clone, PartialEq, Eq, Default)]
1384pub struct UploadPackFeatures {
1385 pub multi_ack: bool,
1386 pub multi_ack_detailed: bool,
1387 pub no_done: bool,
1388 pub thin_pack: bool,
1389 pub side_band: bool,
1390 pub side_band_64k: bool,
1391 pub ofs_delta: bool,
1392 pub shallow: bool,
1393 pub deepen_since: bool,
1394 pub deepen_not: bool,
1395 pub include_tag: bool,
1396 pub no_progress: bool,
1397 pub allow_tip_sha1_in_want: bool,
1398 pub allow_reachable_sha1_in_want: bool,
1399 pub filter: bool,
1400 pub agent: Option<String>,
1401 pub object_format: Option<ObjectFormat>,
1402 pub symrefs: Vec<String>,
1403 pub unknown: Vec<Capability>,
1404}
1405
1406#[derive(Debug, Clone, PartialEq, Eq, Default)]
1407pub struct UploadPackNegotiationRequest {
1408 pub haves: Vec<ObjectId>,
1409 pub done: bool,
1410}
1411
1412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1413pub enum UploadPackAckStatus {
1414 Continue,
1415 Common,
1416 Ready,
1417}
1418
1419#[derive(Debug, Clone, PartialEq, Eq)]
1420pub enum UploadPackAcknowledgment {
1421 Nak,
1422 Ack {
1423 oid: ObjectId,
1424 status: Option<UploadPackAckStatus>,
1425 },
1426}
1427
1428#[derive(Debug, Clone, PartialEq, Eq, Default)]
1429pub struct UploadPackPackfileResponse {
1430 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1431 pub sideband: Vec<SideBandPacket>,
1432}
1433
1434#[derive(Debug, Clone, PartialEq, Eq, Default)]
1435pub struct UploadPackRawPackfileResponse {
1436 pub acknowledgments: Vec<UploadPackAcknowledgment>,
1437 pub packfile: Vec<u8>,
1438}
1439
1440#[derive(Debug, Clone, PartialEq, Eq)]
1441pub struct ReceivePackCommand {
1442 pub old_id: ObjectId,
1443 pub new_id: ObjectId,
1444 pub name: String,
1445}
1446
1447#[derive(Debug, Clone, PartialEq, Eq, Default)]
1448pub struct ReceivePackRequest {
1449 pub shallow: Vec<ObjectId>,
1450 pub commands: Vec<ReceivePackCommand>,
1451 pub capabilities: Vec<Capability>,
1452}
1453
1454#[derive(Debug, Clone, PartialEq, Eq, Default)]
1455pub struct ReceivePackPushRequest {
1456 pub commands: ReceivePackRequest,
1457 pub push_options: Option<Vec<String>>,
1458 pub packfile: Vec<u8>,
1459}
1460
1461#[derive(Debug, Clone, PartialEq, Eq, Default)]
1462pub struct ReceivePackPushRequestOptions {
1463 pub report_status: bool,
1464 pub report_status_v2: bool,
1465 pub atomic: bool,
1466 pub ofs_delta: bool,
1467 pub side_band_64k: bool,
1468 pub quiet: bool,
1469 pub agent: Option<String>,
1470 pub object_format: Option<ObjectFormat>,
1471 pub push_options: Vec<String>,
1472}
1473
1474#[derive(Debug, Clone, PartialEq, Eq, Default)]
1475pub struct ReceivePackFeatures {
1476 pub report_status: bool,
1477 pub report_status_v2: bool,
1478 pub delete_refs: bool,
1479 pub ofs_delta: bool,
1480 pub atomic: bool,
1481 pub push_options: bool,
1482 pub side_band_64k: bool,
1483 pub quiet: bool,
1484 pub no_thin: bool,
1485 pub agent: Option<String>,
1486 pub object_format: Option<ObjectFormat>,
1487 pub unknown: Vec<Capability>,
1488}
1489
1490#[derive(Debug, Clone, PartialEq, Eq)]
1491pub enum ReceivePackUnpackStatus {
1492 Ok,
1493 Error(String),
1494}
1495
1496#[derive(Debug, Clone, PartialEq, Eq)]
1497pub enum ReceivePackCommandStatus {
1498 Ok { name: String },
1499 Ng { name: String, message: String },
1500}
1501
1502#[derive(Debug, Clone, PartialEq, Eq)]
1503pub struct ReceivePackReportStatus {
1504 pub unpack: ReceivePackUnpackStatus,
1505 pub commands: Vec<ReceivePackCommandStatus>,
1506}
1507
1508#[derive(Debug, Clone, PartialEq, Eq, Default)]
1509pub struct ReceivePackCommandStatusV2Options {
1510 pub refname: Option<String>,
1511 pub old_oid: Option<ObjectId>,
1512 pub new_oid: Option<ObjectId>,
1513 pub forced_update: bool,
1514}
1515
1516#[derive(Debug, Clone, PartialEq, Eq)]
1517pub enum ReceivePackCommandStatusV2 {
1518 Ok {
1519 name: String,
1520 options: ReceivePackCommandStatusV2Options,
1521 },
1522 Ng {
1523 name: String,
1524 message: String,
1525 },
1526}
1527
1528#[derive(Debug, Clone, PartialEq, Eq)]
1529pub struct ReceivePackReportStatusV2 {
1530 pub unpack: ReceivePackUnpackStatus,
1531 pub commands: Vec<ReceivePackCommandStatusV2>,
1532}
1533
1534#[derive(Debug, Clone, PartialEq, Eq)]
1535pub struct ProtocolV2CommandRequest {
1536 pub command: String,
1537 pub capabilities: Vec<Capability>,
1538 pub arguments: Vec<Vec<u8>>,
1539}
1540
1541#[derive(Debug, Clone, PartialEq, Eq)]
1542pub enum ProtocolV2Request {
1543 Command(ProtocolV2CommandRequest),
1544 Done,
1545}
1546
1547#[derive(Debug, Clone, PartialEq, Eq)]
1548pub enum ProtocolV2Command {
1549 LsRefs(ProtocolV2LsRefsRequest),
1550 Fetch(ProtocolV2FetchRequest),
1551 ObjectInfo(ProtocolV2ObjectInfoRequest),
1552 Unknown(ProtocolV2CommandRequest),
1553}
1554
1555#[derive(Debug, Clone, PartialEq, Eq)]
1556pub enum ProtocolV2SessionRequest {
1557 Command(ProtocolV2Command),
1558 Done,
1559}
1560
1561#[derive(Debug, Clone, PartialEq, Eq, Default)]
1562pub struct ProtocolV2CommandOptions {
1563 pub agent: Option<String>,
1564 pub object_format: Option<ObjectFormat>,
1565 pub server_options: Vec<String>,
1566 pub extra: Vec<Capability>,
1567}
1568
1569#[derive(Debug, Clone, PartialEq, Eq, Default)]
1570pub struct ProtocolV2FetchFeatures {
1571 pub shallow: bool,
1572 pub wait_for_done: bool,
1573 pub filter: bool,
1574 pub ref_in_want: bool,
1575 pub sideband_all: bool,
1576 pub packfile_uris: bool,
1577 pub unknown: Vec<String>,
1578}
1579
1580#[derive(Debug, Clone, PartialEq, Eq, Default)]
1581pub struct ProtocolV2LsRefsFeatures {
1582 pub unborn: bool,
1583 pub unknown: Vec<String>,
1584}
1585
1586impl ProtocolV2CommandRequest {
1587 pub fn new(command: impl Into<String>) -> Result<Self> {
1588 let command = command.into();
1589 validate_capability_name(&command)?;
1590 Ok(Self {
1591 command,
1592 capabilities: Vec::new(),
1593 arguments: Vec::new(),
1594 })
1595 }
1596}
1597
1598#[derive(Debug, Clone, PartialEq, Eq, Default)]
1599pub struct ProtocolV2LsRefsRequest {
1600 pub peel: bool,
1601 pub symrefs: bool,
1602 pub unborn: bool,
1603 pub ref_prefixes: Vec<String>,
1604}
1605
1606#[derive(Debug, Clone, PartialEq, Eq)]
1607pub struct ProtocolV2LsRefsRef {
1608 pub oid: ObjectId,
1609 pub name: String,
1610 pub peeled: Option<ObjectId>,
1611 pub symref_target: Option<String>,
1612 pub attributes: Vec<String>,
1613}
1614
1615#[derive(Debug, Clone, PartialEq, Eq)]
1616pub enum ProtocolV2LsRefsRecord {
1617 Ref(ProtocolV2LsRefsRef),
1618 Unborn {
1619 name: String,
1620 symref_target: Option<String>,
1621 attributes: Vec<String>,
1622 },
1623}
1624
1625#[derive(Debug, Clone, PartialEq, Eq, Default)]
1626pub struct ProtocolV2FetchRequest {
1627 pub wants: Vec<ObjectId>,
1628 pub want_refs: Vec<String>,
1629 pub haves: Vec<ObjectId>,
1630 pub shallow: Vec<ObjectId>,
1631 pub deepen: Option<u32>,
1632 pub deepen_since: Option<u64>,
1633 pub deepen_not: Vec<String>,
1634 pub deepen_relative: bool,
1635 pub filter: Option<String>,
1636 pub packfile_uris: Option<String>,
1637 pub thin_pack: bool,
1638 pub no_progress: bool,
1639 pub include_tag: bool,
1640 pub ofs_delta: bool,
1641 pub sideband_all: bool,
1642 pub wait_for_done: bool,
1643 pub done: bool,
1644}
1645
1646#[derive(Debug, Clone, PartialEq, Eq)]
1647pub enum ProtocolV2FetchAcknowledgment {
1648 Nak,
1649 Ack(ObjectId),
1650 Ready,
1651}
1652
1653#[derive(Debug, Clone, PartialEq, Eq)]
1654pub enum ProtocolV2FetchShallowInfo {
1655 Shallow(ObjectId),
1656 Unshallow(ObjectId),
1657}
1658
1659#[derive(Debug, Clone, PartialEq, Eq)]
1660pub struct ProtocolV2FetchWantedRef {
1661 pub oid: ObjectId,
1662 pub name: String,
1663}
1664
1665#[derive(Debug, Clone, PartialEq, Eq)]
1666pub struct ProtocolV2FetchPackfileUri {
1667 pub pack_hash: ObjectId,
1668 pub uri: String,
1669}
1670
1671#[derive(Debug, Clone, PartialEq, Eq)]
1672pub enum ProtocolV2FetchResponseSection {
1673 Acknowledgments(Vec<ProtocolV2FetchAcknowledgment>),
1674 ShallowInfo(Vec<ProtocolV2FetchShallowInfo>),
1675 WantedRefs(Vec<ProtocolV2FetchWantedRef>),
1676 PackfileUris(Vec<ProtocolV2FetchPackfileUri>),
1677 Packfile(Vec<Vec<u8>>),
1678 Unknown { name: String, lines: Vec<Vec<u8>> },
1679}
1680
1681#[derive(Debug, Clone, PartialEq, Eq, Default)]
1682pub struct ProtocolV2FetchSidebandAllResponse {
1683 pub sections: Vec<ProtocolV2FetchResponseSection>,
1684 pub progress: Vec<Vec<u8>>,
1685}
1686
1687#[derive(Debug, Clone, PartialEq, Eq, Default)]
1688pub struct ProtocolV2ObjectInfoRequest {
1689 pub size: bool,
1690 pub oids: Vec<ObjectId>,
1691}
1692
1693#[derive(Debug, Clone, PartialEq, Eq)]
1694pub struct ProtocolV2ObjectInfoRecord {
1695 pub oid: ObjectId,
1696 pub size: u64,
1697}
1698
1699#[derive(Debug, Clone, PartialEq, Eq, Default)]
1700pub struct ProtocolV2ObjectInfoResponse {
1701 pub size: bool,
1702 pub records: Vec<ProtocolV2ObjectInfoRecord>,
1703}
1704
1705impl ProtocolV2LsRefsRequest {
1706 pub fn from_command_request(request: &ProtocolV2CommandRequest) -> Result<Self> {
1707 if request.command != "ls-refs" {
1708 return Err(GitError::InvalidFormat(format!(
1709 "expected ls-refs command, got {}",
1710 request.command
1711 )));
1712 }
1713 let mut out = Self::default();
1714 for argument in &request.arguments {
1715 let text = std::str::from_utf8(argument)
1716 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1717 match text {
1718 "peel" => out.peel = true,
1719 "symrefs" => out.symrefs = true,
1720 "unborn" => out.unborn = true,
1721 value if value.starts_with("ref-prefix ") => {
1722 let prefix = value
1723 .strip_prefix("ref-prefix ")
1724 .ok_or_else(|| GitError::InvalidFormat("invalid ref-prefix".into()))?;
1725 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1726 out.ref_prefixes.push(prefix.to_string());
1727 }
1728 other => {
1729 return Err(GitError::InvalidFormat(format!(
1730 "unsupported ls-refs argument {other}"
1731 )));
1732 }
1733 }
1734 }
1735 Ok(out)
1736 }
1737
1738 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1739 let mut request = ProtocolV2CommandRequest::new("ls-refs")?;
1740 if self.peel {
1741 request.arguments.push(b"peel".to_vec());
1742 }
1743 if self.symrefs {
1744 request.arguments.push(b"symrefs".to_vec());
1745 }
1746 if self.unborn {
1747 request.arguments.push(b"unborn".to_vec());
1748 }
1749 for prefix in &self.ref_prefixes {
1750 validate_protocol_v2_token("ls-refs ref-prefix", prefix)?;
1751 request
1752 .arguments
1753 .push(format!("ref-prefix {prefix}").into_bytes());
1754 }
1755 Ok(request)
1756 }
1757}
1758
1759impl ProtocolV2FetchRequest {
1760 pub fn from_command_request(
1761 format: ObjectFormat,
1762 request: &ProtocolV2CommandRequest,
1763 ) -> Result<Self> {
1764 if request.command != "fetch" {
1765 return Err(GitError::InvalidFormat(format!(
1766 "expected fetch command, got {}",
1767 request.command
1768 )));
1769 }
1770 let mut out = Self::default();
1771 for argument in &request.arguments {
1772 let text = std::str::from_utf8(argument)
1773 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
1774 match text {
1775 "thin-pack" => out.thin_pack = true,
1776 "no-progress" => out.no_progress = true,
1777 "include-tag" => out.include_tag = true,
1778 "ofs-delta" => out.ofs_delta = true,
1779 "sideband-all" => out.sideband_all = true,
1780 "wait-for-done" => out.wait_for_done = true,
1781 "deepen-relative" => out.deepen_relative = true,
1782 "done" => out.done = true,
1783 value if value.starts_with("want ") => {
1784 out.wants
1785 .push(parse_oid_argument(format, "fetch want", value, "want ")?);
1786 }
1787 value if value.starts_with("want-ref ") => {
1788 let name = value
1789 .strip_prefix("want-ref ")
1790 .ok_or_else(|| GitError::InvalidFormat("invalid fetch want-ref".into()))?;
1791 validate_protocol_v2_token("fetch want-ref", name)?;
1792 out.want_refs.push(name.to_string());
1793 }
1794 value if value.starts_with("have ") => {
1795 out.haves
1796 .push(parse_oid_argument(format, "fetch have", value, "have ")?);
1797 }
1798 value if value.starts_with("shallow ") => {
1799 out.shallow.push(parse_oid_argument(
1800 format,
1801 "fetch shallow",
1802 value,
1803 "shallow ",
1804 )?);
1805 }
1806 value if value.starts_with("deepen ") => {
1807 if out.deepen.is_some() {
1808 return Err(GitError::InvalidFormat(
1809 "fetch request has duplicate deepen".into(),
1810 ));
1811 }
1812 out.deepen = Some(parse_u32_argument("fetch deepen", value, "deepen ")?);
1813 }
1814 value if value.starts_with("deepen-since ") => {
1815 if out.deepen_since.is_some() {
1816 return Err(GitError::InvalidFormat(
1817 "fetch request has duplicate deepen-since".into(),
1818 ));
1819 }
1820 out.deepen_since = Some(parse_u64_argument(
1821 "fetch deepen-since",
1822 value,
1823 "deepen-since ",
1824 )?);
1825 }
1826 value if value.starts_with("deepen-not ") => {
1827 let name = value.strip_prefix("deepen-not ").ok_or_else(|| {
1828 GitError::InvalidFormat("invalid fetch deepen-not".into())
1829 })?;
1830 validate_protocol_v2_token("fetch deepen-not", name)?;
1831 out.deepen_not.push(name.to_string());
1832 }
1833 value if value.starts_with("filter ") => {
1834 if out.filter.is_some() {
1835 return Err(GitError::InvalidFormat(
1836 "fetch request has duplicate filter".into(),
1837 ));
1838 }
1839 let filter = value
1840 .strip_prefix("filter ")
1841 .ok_or_else(|| GitError::InvalidFormat("invalid fetch filter".into()))?;
1842 validate_protocol_v2_token("fetch filter", filter)?;
1843 out.filter = Some(filter.to_string());
1844 }
1845 value if value.starts_with("packfile-uris ") => {
1846 if out.packfile_uris.is_some() {
1847 return Err(GitError::InvalidFormat(
1848 "fetch request has duplicate packfile-uris".into(),
1849 ));
1850 }
1851 let protocols = value.strip_prefix("packfile-uris ").ok_or_else(|| {
1852 GitError::InvalidFormat("invalid fetch packfile-uris".into())
1853 })?;
1854 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
1855 out.packfile_uris = Some(protocols.to_string());
1856 }
1857 other => {
1858 return Err(GitError::InvalidFormat(format!(
1859 "unsupported fetch argument {other}"
1860 )));
1861 }
1862 }
1863 }
1864 Ok(out)
1865 }
1866
1867 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1868 let mut request = ProtocolV2CommandRequest::new("fetch")?;
1869 for oid in &self.wants {
1870 request.arguments.push(format!("want {oid}").into_bytes());
1871 }
1872 for name in &self.want_refs {
1873 validate_protocol_v2_token("fetch want-ref", name)?;
1874 request
1875 .arguments
1876 .push(format!("want-ref {name}").into_bytes());
1877 }
1878 for oid in &self.haves {
1879 request.arguments.push(format!("have {oid}").into_bytes());
1880 }
1881 for oid in &self.shallow {
1882 request
1883 .arguments
1884 .push(format!("shallow {oid}").into_bytes());
1885 }
1886 if let Some(deepen) = self.deepen {
1887 if deepen == 0 {
1888 return Err(GitError::InvalidFormat(
1889 "fetch deepen must be positive".into(),
1890 ));
1891 }
1892 request
1893 .arguments
1894 .push(format!("deepen {deepen}").into_bytes());
1895 }
1896 if let Some(deepen_since) = self.deepen_since {
1897 request
1898 .arguments
1899 .push(format!("deepen-since {deepen_since}").into_bytes());
1900 }
1901 for name in &self.deepen_not {
1902 validate_protocol_v2_token("fetch deepen-not", name)?;
1903 request
1904 .arguments
1905 .push(format!("deepen-not {name}").into_bytes());
1906 }
1907 if self.deepen_relative {
1908 request.arguments.push(b"deepen-relative".to_vec());
1909 }
1910 if let Some(filter) = &self.filter {
1911 validate_protocol_v2_token("fetch filter", filter)?;
1912 request
1913 .arguments
1914 .push(format!("filter {filter}").into_bytes());
1915 }
1916 if let Some(protocols) = &self.packfile_uris {
1917 validate_protocol_v2_token("fetch packfile-uris", protocols)?;
1918 request
1919 .arguments
1920 .push(format!("packfile-uris {protocols}").into_bytes());
1921 }
1922 if self.thin_pack {
1923 request.arguments.push(b"thin-pack".to_vec());
1924 }
1925 if self.no_progress {
1926 request.arguments.push(b"no-progress".to_vec());
1927 }
1928 if self.include_tag {
1929 request.arguments.push(b"include-tag".to_vec());
1930 }
1931 if self.ofs_delta {
1932 request.arguments.push(b"ofs-delta".to_vec());
1933 }
1934 if self.sideband_all {
1935 request.arguments.push(b"sideband-all".to_vec());
1936 }
1937 if self.wait_for_done {
1938 request.arguments.push(b"wait-for-done".to_vec());
1939 }
1940 if self.done {
1941 request.arguments.push(b"done".to_vec());
1942 }
1943 Ok(request)
1944 }
1945}
1946
1947impl ProtocolV2ObjectInfoRequest {
1948 pub fn from_command_request(
1949 format: ObjectFormat,
1950 request: &ProtocolV2CommandRequest,
1951 ) -> Result<Self> {
1952 if request.command != "object-info" {
1953 return Err(GitError::InvalidFormat(format!(
1954 "expected object-info command, got {}",
1955 request.command
1956 )));
1957 }
1958 let mut out = Self::default();
1959 for argument in &request.arguments {
1960 let text = parse_protocol_v2_line_text("object-info request argument", argument)?;
1961 if text == "size" {
1962 if out.size {
1963 return Err(GitError::InvalidFormat(
1964 "object-info request has duplicate size argument".into(),
1965 ));
1966 }
1967 out.size = true;
1968 } else if text.starts_with("oid ") {
1969 out.oids
1970 .push(parse_oid_argument(format, "object-info oid", text, "oid ")?);
1971 } else {
1972 return Err(GitError::InvalidFormat(format!(
1973 "unsupported object-info request argument {text}"
1974 )));
1975 }
1976 }
1977 if !out.size {
1978 return Err(GitError::InvalidFormat(
1979 "object-info request is missing size argument".into(),
1980 ));
1981 }
1982 if out.oids.is_empty() {
1983 return Err(GitError::InvalidFormat(
1984 "object-info request is missing object ids".into(),
1985 ));
1986 }
1987 Ok(out)
1988 }
1989
1990 pub fn to_command_request(&self) -> Result<ProtocolV2CommandRequest> {
1991 if !self.size {
1992 return Err(GitError::InvalidFormat(
1993 "object-info request is missing size argument".into(),
1994 ));
1995 }
1996 if self.oids.is_empty() {
1997 return Err(GitError::InvalidFormat(
1998 "object-info request is missing object ids".into(),
1999 ));
2000 }
2001 let mut request = ProtocolV2CommandRequest::new("object-info")?;
2002 request.arguments.push(b"size".to_vec());
2003 for oid in &self.oids {
2004 request.arguments.push(format!("oid {oid}").into_bytes());
2005 }
2006 Ok(request)
2007 }
2008}
2009
2010pub fn parse_protocol_v2_advertisement(frames: &[PktLineFrame]) -> Result<TransportHandshake> {
2011 let Some((first, rest)) = frames.split_first() else {
2012 return Err(GitError::InvalidFormat(
2013 "protocol v2 advertisement is empty".into(),
2014 ));
2015 };
2016 match first {
2017 PktLineFrame::Data(payload) if trim_trailing_lf(payload) == b"version 2" => {}
2018 PktLineFrame::Data(_) => {
2019 return Err(GitError::InvalidFormat(
2020 "protocol v2 advertisement missing version line".into(),
2021 ));
2022 }
2023 _ => {
2024 return Err(GitError::InvalidFormat(
2025 "protocol v2 advertisement must start with a data line".into(),
2026 ));
2027 }
2028 }
2029
2030 let mut capabilities = Vec::new();
2031 let mut saw_flush = false;
2032 for (idx, frame) in rest.iter().enumerate() {
2033 match frame {
2034 PktLineFrame::Data(payload) => {
2035 if saw_flush {
2036 return Err(GitError::InvalidFormat(
2037 "protocol v2 advertisement has data after flush".into(),
2038 ));
2039 }
2040 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2041 }
2042 PktLineFrame::Flush => {
2043 saw_flush = true;
2044 if idx + 1 != rest.len() {
2045 return Err(GitError::InvalidFormat(
2046 "protocol v2 advertisement has frames after flush".into(),
2047 ));
2048 }
2049 }
2050 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2051 return Err(GitError::InvalidFormat(
2052 "protocol v2 advertisement contains a non-flush control packet".into(),
2053 ));
2054 }
2055 }
2056 }
2057 if !saw_flush {
2058 return Err(GitError::InvalidFormat(
2059 "protocol v2 advertisement missing flush".into(),
2060 ));
2061 }
2062
2063 Ok(TransportHandshake {
2064 protocol: ProtocolVersion::V2,
2065 capabilities,
2066 })
2067}
2068
2069pub fn encode_protocol_v2_advertisement(
2070 handshake: &TransportHandshake,
2071) -> Result<Vec<PktLineFrame>> {
2072 if handshake.protocol != ProtocolVersion::V2 {
2073 return Err(GitError::InvalidFormat(
2074 "protocol v2 advertisement requires a v2 handshake".into(),
2075 ));
2076 }
2077 let mut frames = vec![PktLineFrame::data(line_from_str("version 2"))?];
2078 for capability in &handshake.capabilities {
2079 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2080 capability,
2081 )?))?);
2082 }
2083 frames.push(PktLineFrame::Flush);
2084 Ok(frames)
2085}
2086
2087pub fn read_protocol_v2_advertisement(reader: &mut impl Read) -> Result<TransportHandshake> {
2088 let frames = read_pkt_line_frames_until_flush(reader)?;
2089 parse_protocol_v2_advertisement(&frames)
2090}
2091
2092pub fn write_protocol_v2_advertisement(
2093 writer: &mut impl Write,
2094 handshake: &TransportHandshake,
2095) -> Result<()> {
2096 if handshake.protocol != ProtocolVersion::V2 {
2097 return Err(GitError::InvalidFormat(
2098 "protocol v2 advertisement requires a v2 handshake".into(),
2099 ));
2100 }
2101 write_pkt_line_payload(writer, b"version 2\n")?;
2102 for capability in &handshake.capabilities {
2103 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2104 }
2105 writer.write_all(b"0000")?;
2106 Ok(())
2107}
2108
2109pub fn parse_protocol_v2_command_request(
2110 frames: &[PktLineFrame],
2111) -> Result<ProtocolV2CommandRequest> {
2112 let Some((first, rest)) = frames.split_first() else {
2113 return Err(GitError::InvalidFormat(
2114 "protocol v2 command request is empty".into(),
2115 ));
2116 };
2117 let command = match first {
2118 PktLineFrame::Data(payload) => parse_protocol_v2_command_line(payload)?,
2119 _ => {
2120 return Err(GitError::InvalidFormat(
2121 "protocol v2 command request must start with a command line".into(),
2122 ));
2123 }
2124 };
2125
2126 let mut capabilities = Vec::new();
2127 let mut arguments = Vec::new();
2128 let mut in_arguments = false;
2129 let mut saw_flush = false;
2130 for (idx, frame) in rest.iter().enumerate() {
2131 match frame {
2132 PktLineFrame::Data(payload) if !in_arguments => {
2133 if saw_flush {
2134 return Err(GitError::InvalidFormat(
2135 "protocol v2 command request has data after flush".into(),
2136 ));
2137 }
2138 capabilities.push(parse_protocol_v2_capability_line(payload)?);
2139 }
2140 PktLineFrame::Data(payload) => {
2141 if saw_flush {
2142 return Err(GitError::InvalidFormat(
2143 "protocol v2 command request has data after flush".into(),
2144 ));
2145 }
2146 let argument = trim_trailing_lf(payload);
2147 if argument.is_empty() {
2148 return Err(GitError::InvalidFormat(
2149 "protocol v2 command argument is empty".into(),
2150 ));
2151 }
2152 if argument
2153 .iter()
2154 .any(|byte| matches!(*byte, b'\n' | b'\r' | 0))
2155 {
2156 return Err(GitError::InvalidFormat(
2157 "protocol v2 command argument contains a delimiter byte".into(),
2158 ));
2159 }
2160 arguments.push(argument.to_vec());
2161 }
2162 PktLineFrame::Delimiter => {
2163 if in_arguments {
2164 return Err(GitError::InvalidFormat(
2165 "protocol v2 command request has duplicate delimiter".into(),
2166 ));
2167 }
2168 if saw_flush {
2169 return Err(GitError::InvalidFormat(
2170 "protocol v2 command request has delimiter after flush".into(),
2171 ));
2172 }
2173 in_arguments = true;
2174 }
2175 PktLineFrame::Flush => {
2176 saw_flush = true;
2177 if idx + 1 != rest.len() {
2178 return Err(GitError::InvalidFormat(
2179 "protocol v2 command request has frames after flush".into(),
2180 ));
2181 }
2182 }
2183 PktLineFrame::ResponseEnd => {
2184 return Err(GitError::InvalidFormat(
2185 "protocol v2 command request contains response-end".into(),
2186 ));
2187 }
2188 }
2189 }
2190 if !saw_flush {
2191 return Err(GitError::InvalidFormat(
2192 "protocol v2 command request missing flush".into(),
2193 ));
2194 }
2195
2196 Ok(ProtocolV2CommandRequest {
2197 command,
2198 capabilities,
2199 arguments,
2200 })
2201}
2202
2203pub fn encode_protocol_v2_command_request(
2204 request: &ProtocolV2CommandRequest,
2205) -> Result<Vec<PktLineFrame>> {
2206 validate_capability_name(&request.command)?;
2207 let mut frames = Vec::new();
2208 frames.push(PktLineFrame::data(line_from_str(&format!(
2209 "command={}",
2210 request.command
2211 )))?);
2212 for capability in &request.capabilities {
2213 frames.push(PktLineFrame::data(line(encode_protocol_v2_capability(
2214 capability,
2215 )?))?);
2216 }
2217 if !request.arguments.is_empty() {
2218 frames.push(PktLineFrame::Delimiter);
2219 for argument in &request.arguments {
2220 validate_protocol_v2_argument(argument)?;
2221 let mut payload = argument.clone();
2222 payload.push(b'\n');
2223 frames.push(PktLineFrame::data(payload)?);
2224 }
2225 }
2226 frames.push(PktLineFrame::Flush);
2227 Ok(frames)
2228}
2229
2230pub fn parse_protocol_v2_request(frames: &[PktLineFrame]) -> Result<ProtocolV2Request> {
2231 if matches!(frames, [PktLineFrame::Flush]) {
2232 return Ok(ProtocolV2Request::Done);
2233 }
2234 parse_protocol_v2_command_request(frames).map(ProtocolV2Request::Command)
2235}
2236
2237pub fn encode_protocol_v2_request(request: &ProtocolV2Request) -> Result<Vec<PktLineFrame>> {
2238 match request {
2239 ProtocolV2Request::Command(command) => encode_protocol_v2_command_request(command),
2240 ProtocolV2Request::Done => Ok(vec![PktLineFrame::Flush]),
2241 }
2242}
2243
2244pub fn read_protocol_v2_request(reader: &mut impl Read) -> Result<ProtocolV2Request> {
2245 let frames = read_pkt_line_frames_until_flush(reader)?;
2246 parse_protocol_v2_request(&frames)
2247}
2248
2249pub fn write_protocol_v2_request(
2250 writer: &mut impl Write,
2251 request: &ProtocolV2Request,
2252) -> Result<()> {
2253 match request {
2254 ProtocolV2Request::Command(command) => write_protocol_v2_command_request(writer, command),
2255 ProtocolV2Request::Done => {
2256 writer.write_all(b"0000")?;
2257 Ok(())
2258 }
2259 }
2260}
2261
2262pub fn read_protocol_v2_command_request(
2263 reader: &mut impl Read,
2264) -> Result<ProtocolV2CommandRequest> {
2265 let frames = read_pkt_line_frames_until_flush(reader)?;
2266 parse_protocol_v2_command_request(&frames)
2267}
2268
2269pub fn write_protocol_v2_command_request(
2270 writer: &mut impl Write,
2271 request: &ProtocolV2CommandRequest,
2272) -> Result<()> {
2273 validate_capability_name(&request.command)?;
2274 write_pkt_line_payload(
2275 writer,
2276 &line_from_str(&format!("command={}", request.command)),
2277 )?;
2278 for capability in &request.capabilities {
2279 write_pkt_line_payload(writer, &line(encode_protocol_v2_capability(capability)?))?;
2280 }
2281 if !request.arguments.is_empty() {
2282 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2283 for argument in &request.arguments {
2284 validate_protocol_v2_argument(argument)?;
2285 let mut payload = argument.clone();
2286 payload.push(b'\n');
2287 write_pkt_line_payload(writer, &payload)?;
2288 }
2289 }
2290 writer.write_all(b"0000")?;
2291 Ok(())
2292}
2293
2294pub fn read_protocol_v2_ls_refs_request(reader: &mut impl Read) -> Result<ProtocolV2LsRefsRequest> {
2295 let request = read_protocol_v2_command_request(reader)?;
2296 ProtocolV2LsRefsRequest::from_command_request(&request)
2297}
2298
2299pub fn write_protocol_v2_ls_refs_request(
2300 writer: &mut impl Write,
2301 request: &ProtocolV2LsRefsRequest,
2302) -> Result<()> {
2303 let command = request.to_command_request()?;
2304 write_protocol_v2_command_request(writer, &command)
2305}
2306
2307pub fn parse_protocol_v2_ls_refs_response(
2308 format: ObjectFormat,
2309 frames: &[PktLineFrame],
2310) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2311 let mut records = Vec::new();
2312 let mut saw_flush = false;
2313 for (idx, frame) in frames.iter().enumerate() {
2314 match frame {
2315 PktLineFrame::Data(payload) => {
2316 if saw_flush {
2317 return Err(GitError::InvalidFormat(
2318 "ls-refs response has data after flush".into(),
2319 ));
2320 }
2321 records.push(parse_protocol_v2_ls_refs_line(format, payload)?);
2322 }
2323 PktLineFrame::Flush => {
2324 saw_flush = true;
2325 if !flush_terminates_protocol_v2_response(frames, idx) {
2326 return Err(GitError::InvalidFormat(
2327 "ls-refs response has frames after flush".into(),
2328 ));
2329 }
2330 }
2331 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2332 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2333 return Err(GitError::InvalidFormat(
2334 "ls-refs response contains a non-flush control packet".into(),
2335 ));
2336 }
2337 }
2338 }
2339 if !saw_flush {
2340 return Err(GitError::InvalidFormat(
2341 "ls-refs response missing flush".into(),
2342 ));
2343 }
2344 Ok(records)
2345}
2346
2347pub fn encode_protocol_v2_ls_refs_response(
2348 records: &[ProtocolV2LsRefsRecord],
2349) -> Result<Vec<PktLineFrame>> {
2350 let mut frames = Vec::new();
2351 for record in records {
2352 frames.push(PktLineFrame::data(line_from_str(
2353 &format_protocol_v2_ls_refs_record(record)?,
2354 ))?);
2355 }
2356 frames.push(PktLineFrame::Flush);
2357 Ok(frames)
2358}
2359
2360pub fn read_protocol_v2_ls_refs_response(
2361 format: ObjectFormat,
2362 reader: &mut impl Read,
2363) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2364 let frames = read_pkt_line_frames_until_flush(reader)?;
2365 parse_protocol_v2_ls_refs_response(format, &frames)
2366}
2367
2368pub fn write_protocol_v2_ls_refs_response(
2369 writer: &mut impl Write,
2370 records: &[ProtocolV2LsRefsRecord],
2371) -> Result<()> {
2372 for record in records {
2373 write_pkt_line_payload(
2374 writer,
2375 &line_from_str(&format_protocol_v2_ls_refs_record(record)?),
2376 )?;
2377 }
2378 writer.write_all(b"0000")?;
2379 Ok(())
2380}
2381
2382pub fn read_protocol_v2_ls_refs_response_until_response_end(
2383 format: ObjectFormat,
2384 reader: &mut impl Read,
2385) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2386 let frames = read_pkt_line_frames_until_response_end(reader)?;
2387 parse_protocol_v2_ls_refs_response(format, &frames)
2388}
2389
2390pub fn write_protocol_v2_ls_refs_response_with_response_end(
2391 writer: &mut impl Write,
2392 records: &[ProtocolV2LsRefsRecord],
2393) -> Result<()> {
2394 write_protocol_v2_ls_refs_response(writer, records)?;
2395 writer.write_all(b"0002")?;
2396 Ok(())
2397}
2398
2399pub fn exchange_protocol_v2_ls_refs(
2400 format: ObjectFormat,
2401 reader: &mut impl Read,
2402 writer: &mut impl Write,
2403 request: &ProtocolV2LsRefsRequest,
2404) -> Result<Vec<ProtocolV2LsRefsRecord>> {
2405 write_protocol_v2_ls_refs_request(writer, request)?;
2406 writer.flush()?;
2407 read_protocol_v2_ls_refs_response(format, reader)
2408}
2409
2410pub fn protocol_v2_ls_refs_records_to_ref_advertisement_set(
2426 records: &[ProtocolV2LsRefsRecord],
2427) -> Result<RefAdvertisementSet> {
2428 let mut refs: Vec<RefAdvertisement> = Vec::new();
2429 let mut symrefs: Vec<Capability> = Vec::new();
2430 for record in records {
2431 match record {
2432 ProtocolV2LsRefsRecord::Ref(reference) => {
2433 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
2434 refs.push(RefAdvertisement {
2435 oid: reference.oid,
2436 name: reference.name.clone(),
2437 capabilities: Vec::new(),
2438 });
2439 if let Some(peeled) = &reference.peeled {
2440 refs.push(RefAdvertisement {
2441 oid: peeled.clone(),
2442 name: format!("{}^{{}}", reference.name),
2443 capabilities: Vec::new(),
2444 });
2445 }
2446 if let Some(target) = &reference.symref_target {
2447 symrefs.push(protocol_v2_symref_capability(&reference.name, target)?);
2448 }
2449 }
2450 ProtocolV2LsRefsRecord::Unborn {
2451 name,
2452 symref_target,
2453 ..
2454 } => {
2455 validate_protocol_v2_token("ls-refs ref name", name)?;
2456 if let Some(target) = symref_target {
2457 symrefs.push(protocol_v2_symref_capability(name, target)?);
2458 }
2459 }
2460 }
2461 }
2462 if !symrefs.is_empty() {
2463 if let Some(first) = refs.first_mut() {
2464 first.capabilities = symrefs;
2465 } else {
2466 return Err(GitError::InvalidFormat(
2467 "ls-refs response advertised symrefs without any concrete refs".into(),
2468 ));
2469 }
2470 }
2471 Ok(RefAdvertisementSet {
2472 protocol: ProtocolVersion::V2,
2473 refs,
2474 shallow: Vec::new(),
2475 })
2476}
2477
2478pub fn parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2483 format: ObjectFormat,
2484 frames: &[PktLineFrame],
2485) -> Result<RefAdvertisementSet> {
2486 let records = parse_protocol_v2_ls_refs_response(format, frames)?;
2487 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2488}
2489
2490pub fn read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
2493 format: ObjectFormat,
2494 reader: &mut impl Read,
2495) -> Result<RefAdvertisementSet> {
2496 let records = read_protocol_v2_ls_refs_response(format, reader)?;
2497 protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
2498}
2499
2500fn protocol_v2_symref_capability(name: &str, target: &str) -> Result<Capability> {
2501 validate_protocol_v2_token("ls-refs symref-target", target)?;
2502 Ok(Capability {
2503 name: "symref".into(),
2504 value: Some(format!("{name}:{target}")),
2505 })
2506}
2507
2508pub fn read_protocol_v2_fetch_request(
2509 format: ObjectFormat,
2510 reader: &mut impl Read,
2511) -> Result<ProtocolV2FetchRequest> {
2512 let request = read_protocol_v2_command_request(reader)?;
2513 ProtocolV2FetchRequest::from_command_request(format, &request)
2514}
2515
2516pub fn write_protocol_v2_fetch_request(
2517 writer: &mut impl Write,
2518 request: &ProtocolV2FetchRequest,
2519) -> Result<()> {
2520 let command = request.to_command_request()?;
2521 write_protocol_v2_command_request(writer, &command)
2522}
2523
2524pub fn read_protocol_v2_object_info_request(
2525 format: ObjectFormat,
2526 reader: &mut impl Read,
2527) -> Result<ProtocolV2ObjectInfoRequest> {
2528 let request = read_protocol_v2_command_request(reader)?;
2529 ProtocolV2ObjectInfoRequest::from_command_request(format, &request)
2530}
2531
2532pub fn write_protocol_v2_object_info_request(
2533 writer: &mut impl Write,
2534 request: &ProtocolV2ObjectInfoRequest,
2535) -> Result<()> {
2536 let command = request.to_command_request()?;
2537 write_protocol_v2_command_request(writer, &command)
2538}
2539
2540pub fn parse_protocol_v2_fetch_response(
2541 format: ObjectFormat,
2542 frames: &[PktLineFrame],
2543) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2544 let mut sections = Vec::new();
2545 let mut current: Option<(String, Vec<Vec<u8>>)> = None;
2546 let mut saw_flush = false;
2547 for (idx, frame) in frames.iter().enumerate() {
2548 match frame {
2549 PktLineFrame::Data(payload) => {
2550 if saw_flush {
2551 return Err(GitError::InvalidFormat(
2552 "fetch response has data after flush".into(),
2553 ));
2554 }
2555 if let Some((_name, lines)) = &mut current {
2556 lines.push(payload.clone());
2557 } else {
2558 let name = parse_fetch_section_header(payload)?;
2559 current = Some((name, Vec::new()));
2560 }
2561 }
2562 PktLineFrame::Delimiter => {
2563 if saw_flush {
2564 return Err(GitError::InvalidFormat(
2565 "fetch response has delimiter after flush".into(),
2566 ));
2567 }
2568 let Some((name, lines)) = current.take() else {
2569 return Err(GitError::InvalidFormat(
2570 "fetch response has delimiter before section".into(),
2571 ));
2572 };
2573 sections.push(parse_fetch_section(format, name, lines)?);
2574 }
2575 PktLineFrame::Flush => {
2576 saw_flush = true;
2577 if !flush_terminates_protocol_v2_response(frames, idx) {
2578 return Err(GitError::InvalidFormat(
2579 "fetch response has frames after flush".into(),
2580 ));
2581 }
2582 if let Some((name, lines)) = current.take() {
2583 sections.push(parse_fetch_section(format, name, lines)?);
2584 }
2585 }
2586 PktLineFrame::ResponseEnd if saw_flush && idx + 1 == frames.len() => {}
2587 PktLineFrame::ResponseEnd => {
2588 return Err(GitError::InvalidFormat(
2589 "fetch response contains response-end".into(),
2590 ));
2591 }
2592 }
2593 }
2594 if !saw_flush {
2595 return Err(GitError::InvalidFormat(
2596 "fetch response missing flush".into(),
2597 ));
2598 }
2599 Ok(sections)
2600}
2601
2602pub fn encode_protocol_v2_fetch_response(
2603 sections: &[ProtocolV2FetchResponseSection],
2604) -> Result<Vec<PktLineFrame>> {
2605 let mut frames = Vec::new();
2606 for (idx, section) in sections.iter().enumerate() {
2607 if idx != 0 {
2608 frames.push(PktLineFrame::Delimiter);
2609 }
2610 frames.push(PktLineFrame::data(line_from_str(
2611 protocol_v2_fetch_section_name(section),
2612 ))?);
2613 for line in format_protocol_v2_fetch_section_lines(section)? {
2614 frames.push(PktLineFrame::data(line)?);
2615 }
2616 }
2617 frames.push(PktLineFrame::Flush);
2618 Ok(frames)
2619}
2620
2621pub fn parse_protocol_v2_fetch_sideband_all_response(
2622 format: ObjectFormat,
2623 frames: &[PktLineFrame],
2624) -> Result<ProtocolV2FetchSidebandAllResponse> {
2625 let mut demuxed = Vec::new();
2626 let mut progress = Vec::new();
2627 let mut in_packfile = false;
2628 for frame in frames {
2629 match frame {
2630 PktLineFrame::Data(payload) if in_packfile => {
2631 demuxed.push(PktLineFrame::Data(payload.clone()));
2632 }
2633 PktLineFrame::Data(payload) => {
2634 let packet = parse_sideband_packet(payload)?;
2635 match packet.channel {
2636 SideBandChannel::Data => {
2637 if trim_trailing_lf(&packet.data) == b"packfile" {
2638 in_packfile = true;
2639 }
2640 demuxed.push(PktLineFrame::Data(packet.data));
2641 }
2642 SideBandChannel::Progress => progress.push(packet.data),
2643 SideBandChannel::Fatal => {
2644 let message = String::from_utf8_lossy(&packet.data).into_owned();
2645 return Err(GitError::InvalidFormat(format!(
2646 "sideband fatal: {message}"
2647 )));
2648 }
2649 }
2650 }
2651 PktLineFrame::Delimiter => {
2652 in_packfile = false;
2653 demuxed.push(PktLineFrame::Delimiter);
2654 }
2655 PktLineFrame::Flush => {
2656 in_packfile = false;
2657 demuxed.push(PktLineFrame::Flush);
2658 }
2659 PktLineFrame::ResponseEnd => {
2660 in_packfile = false;
2661 demuxed.push(PktLineFrame::ResponseEnd);
2662 }
2663 }
2664 }
2665 Ok(ProtocolV2FetchSidebandAllResponse {
2666 sections: parse_protocol_v2_fetch_response(format, &demuxed)?,
2667 progress,
2668 })
2669}
2670
2671pub fn encode_protocol_v2_fetch_sideband_all_response(
2672 sections: &[ProtocolV2FetchResponseSection],
2673) -> Result<Vec<PktLineFrame>> {
2674 let frames = encode_protocol_v2_fetch_response(sections)?;
2675 let mut encoded = Vec::new();
2676 let mut in_packfile = false;
2677 for frame in frames {
2678 match frame {
2679 PktLineFrame::Data(payload) if in_packfile => {
2680 encoded.push(PktLineFrame::Data(payload));
2681 }
2682 PktLineFrame::Data(payload) => {
2683 if trim_trailing_lf(&payload) == b"packfile" {
2684 in_packfile = true;
2685 }
2686 encoded.push(PktLineFrame::data(encode_sideband_packet(
2687 &SideBandPacket {
2688 channel: SideBandChannel::Data,
2689 data: payload,
2690 },
2691 )?)?);
2692 }
2693 PktLineFrame::Delimiter => {
2694 in_packfile = false;
2695 encoded.push(PktLineFrame::Delimiter);
2696 }
2697 PktLineFrame::Flush => {
2698 in_packfile = false;
2699 encoded.push(PktLineFrame::Flush);
2700 }
2701 PktLineFrame::ResponseEnd => {
2702 in_packfile = false;
2703 encoded.push(PktLineFrame::ResponseEnd);
2704 }
2705 }
2706 }
2707 Ok(encoded)
2708}
2709
2710pub fn read_protocol_v2_fetch_response(
2711 format: ObjectFormat,
2712 reader: &mut impl Read,
2713) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2714 let frames = read_pkt_line_frames_until_flush(reader)?;
2715 parse_protocol_v2_fetch_response(format, &frames)
2716}
2717
2718pub fn write_protocol_v2_fetch_response(
2719 writer: &mut impl Write,
2720 sections: &[ProtocolV2FetchResponseSection],
2721) -> Result<()> {
2722 write_protocol_v2_fetch_response_inner(writer, sections, false, false)
2723}
2724
2725pub fn read_protocol_v2_fetch_sideband_all_response(
2726 format: ObjectFormat,
2727 reader: &mut impl Read,
2728) -> Result<ProtocolV2FetchSidebandAllResponse> {
2729 let frames = read_pkt_line_frames_until_flush(reader)?;
2730 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2731}
2732
2733pub fn write_protocol_v2_fetch_sideband_all_response(
2734 writer: &mut impl Write,
2735 sections: &[ProtocolV2FetchResponseSection],
2736) -> Result<()> {
2737 write_protocol_v2_fetch_response_inner(writer, sections, true, false)
2738}
2739
2740pub fn read_protocol_v2_fetch_response_until_response_end(
2741 format: ObjectFormat,
2742 reader: &mut impl Read,
2743) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2744 let frames = read_pkt_line_frames_until_response_end(reader)?;
2745 parse_protocol_v2_fetch_response(format, &frames)
2746}
2747
2748pub fn write_protocol_v2_fetch_response_with_response_end(
2749 writer: &mut impl Write,
2750 sections: &[ProtocolV2FetchResponseSection],
2751) -> Result<()> {
2752 write_protocol_v2_fetch_response_inner(writer, sections, false, true)
2753}
2754
2755pub fn read_protocol_v2_fetch_sideband_all_response_until_response_end(
2756 format: ObjectFormat,
2757 reader: &mut impl Read,
2758) -> Result<ProtocolV2FetchSidebandAllResponse> {
2759 let frames = read_pkt_line_frames_until_response_end(reader)?;
2760 parse_protocol_v2_fetch_sideband_all_response(format, &frames)
2761}
2762
2763pub fn write_protocol_v2_fetch_sideband_all_response_with_response_end(
2764 writer: &mut impl Write,
2765 sections: &[ProtocolV2FetchResponseSection],
2766) -> Result<()> {
2767 write_protocol_v2_fetch_response_inner(writer, sections, true, true)
2768}
2769
2770fn write_protocol_v2_fetch_response_inner(
2771 writer: &mut impl Write,
2772 sections: &[ProtocolV2FetchResponseSection],
2773 sideband_all: bool,
2774 response_end: bool,
2775) -> Result<()> {
2776 let mut in_packfile = false;
2777 for (idx, section) in sections.iter().enumerate() {
2778 if idx != 0 {
2779 in_packfile = false;
2780 write_pkt_line_frame(writer, &PktLineFrame::Delimiter)?;
2781 }
2782 write_protocol_v2_fetch_payload(
2783 writer,
2784 &line_from_str(protocol_v2_fetch_section_name(section)),
2785 sideband_all,
2786 &mut in_packfile,
2787 )?;
2788 for payload in format_protocol_v2_fetch_section_lines(section)? {
2789 write_protocol_v2_fetch_payload(writer, &payload, sideband_all, &mut in_packfile)?;
2790 }
2791 }
2792 writer.write_all(b"0000")?;
2793 if response_end {
2794 writer.write_all(b"0002")?;
2795 }
2796 Ok(())
2797}
2798
2799fn write_protocol_v2_fetch_payload(
2800 writer: &mut impl Write,
2801 payload: &[u8],
2802 sideband_all: bool,
2803 in_packfile: &mut bool,
2804) -> Result<()> {
2805 if sideband_all && !*in_packfile {
2806 if trim_trailing_lf(payload) == b"packfile" {
2807 *in_packfile = true;
2808 }
2809 write_sideband_payload(writer, SideBandChannel::Data, payload)
2810 } else {
2811 write_pkt_line_payload(writer, payload)
2812 }
2813}
2814
2815pub fn exchange_protocol_v2_fetch(
2816 format: ObjectFormat,
2817 reader: &mut impl Read,
2818 writer: &mut impl Write,
2819 request: &ProtocolV2FetchRequest,
2820) -> Result<Vec<ProtocolV2FetchResponseSection>> {
2821 write_protocol_v2_fetch_request(writer, request)?;
2822 writer.flush()?;
2823 read_protocol_v2_fetch_response(format, reader)
2824}
2825
2826pub fn parse_protocol_v2_object_info_response(
2827 format: ObjectFormat,
2828 frames: &[PktLineFrame],
2829) -> Result<ProtocolV2ObjectInfoResponse> {
2830 let Some((first, rest)) = frames.split_first() else {
2831 return Err(GitError::InvalidFormat(
2832 "object-info response is empty".into(),
2833 ));
2834 };
2835 let PktLineFrame::Data(attrs) = first else {
2836 return Err(GitError::InvalidFormat(
2837 "object-info response must start with attributes".into(),
2838 ));
2839 };
2840 let attrs = parse_protocol_v2_line_text("object-info response attributes", attrs)?;
2841 let mut response = ProtocolV2ObjectInfoResponse::default();
2842 for attr in attrs.split(' ') {
2843 validate_protocol_v2_token("object-info response attribute", attr)?;
2844 match attr {
2845 "size" => {
2846 if response.size {
2847 return Err(GitError::InvalidFormat(
2848 "object-info response has duplicate size attribute".into(),
2849 ));
2850 }
2851 response.size = true;
2852 }
2853 other => {
2854 return Err(GitError::InvalidFormat(format!(
2855 "unsupported object-info response attribute {other}"
2856 )));
2857 }
2858 }
2859 }
2860 if !response.size {
2861 return Err(GitError::InvalidFormat(
2862 "object-info response is missing size attribute".into(),
2863 ));
2864 }
2865
2866 let mut saw_flush = false;
2867 for (idx, frame) in rest.iter().enumerate() {
2868 match frame {
2869 PktLineFrame::Data(payload) if !saw_flush => {
2870 response
2871 .records
2872 .push(parse_protocol_v2_object_info_record(format, payload)?);
2873 }
2874 PktLineFrame::Data(_) => {
2875 return Err(GitError::InvalidFormat(
2876 "object-info response has data after flush".into(),
2877 ));
2878 }
2879 PktLineFrame::Flush => {
2880 saw_flush = true;
2881 if idx + 1 != rest.len() {
2882 return Err(GitError::InvalidFormat(
2883 "object-info response has frames after flush".into(),
2884 ));
2885 }
2886 }
2887 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
2888 return Err(GitError::InvalidFormat(
2889 "object-info response contains a non-flush control packet".into(),
2890 ));
2891 }
2892 }
2893 }
2894 if !saw_flush {
2895 return Err(GitError::InvalidFormat(
2896 "object-info response missing flush".into(),
2897 ));
2898 }
2899 Ok(response)
2900}
2901
2902pub fn encode_protocol_v2_object_info_response(
2903 response: &ProtocolV2ObjectInfoResponse,
2904) -> Result<Vec<PktLineFrame>> {
2905 if !response.size {
2906 return Err(GitError::InvalidFormat(
2907 "object-info response is missing size attribute".into(),
2908 ));
2909 }
2910 let mut frames = Vec::new();
2911 frames.push(PktLineFrame::data(line_from_str("size"))?);
2912 for record in &response.records {
2913 frames.push(PktLineFrame::data(line_from_str(&format!(
2914 "{} {}",
2915 record.oid, record.size
2916 )))?);
2917 }
2918 frames.push(PktLineFrame::Flush);
2919 Ok(frames)
2920}
2921
2922pub fn read_protocol_v2_object_info_response(
2923 format: ObjectFormat,
2924 reader: &mut impl Read,
2925) -> Result<ProtocolV2ObjectInfoResponse> {
2926 let frames = read_pkt_line_frames_until_flush(reader)?;
2927 parse_protocol_v2_object_info_response(format, &frames)
2928}
2929
2930pub fn write_protocol_v2_object_info_response(
2931 writer: &mut impl Write,
2932 response: &ProtocolV2ObjectInfoResponse,
2933) -> Result<()> {
2934 if !response.size {
2935 return Err(GitError::InvalidFormat(
2936 "object-info response is missing size attribute".into(),
2937 ));
2938 }
2939 write_pkt_line_payload(writer, b"size\n")?;
2940 for record in &response.records {
2941 write_pkt_line_payload(
2942 writer,
2943 &line_from_str(&format!("{} {}", record.oid, record.size)),
2944 )?;
2945 }
2946 writer.write_all(b"0000")?;
2947 Ok(())
2948}
2949
2950pub fn exchange_protocol_v2_object_info(
2951 format: ObjectFormat,
2952 reader: &mut impl Read,
2953 writer: &mut impl Write,
2954 request: &ProtocolV2ObjectInfoRequest,
2955) -> Result<ProtocolV2ObjectInfoResponse> {
2956 write_protocol_v2_object_info_request(writer, request)?;
2957 writer.flush()?;
2958 read_protocol_v2_object_info_response(format, reader)
2959}
2960
2961pub fn demux_protocol_v2_fetch_packfile(
2962 sections: &[ProtocolV2FetchResponseSection],
2963) -> Result<Option<SideBandDemux>> {
2964 let mut packfile = None;
2965 for section in sections {
2966 if let ProtocolV2FetchResponseSection::Packfile(lines) = section {
2967 if packfile.is_some() {
2968 return Err(GitError::InvalidFormat(
2969 "fetch response has duplicate packfile sections".into(),
2970 ));
2971 }
2972 packfile = Some(parse_and_demux_sideband_packets(lines)?);
2973 }
2974 }
2975 Ok(packfile)
2976}
2977
2978pub fn protocol_v2_object_format(capabilities: &[Capability]) -> Result<ObjectFormat> {
2979 let mut format = None;
2980 for capability in capabilities {
2981 if capability.name != "object-format" {
2982 continue;
2983 }
2984 if format.is_some() {
2985 return Err(GitError::InvalidFormat(
2986 "protocol v2 has duplicate object-format capabilities".into(),
2987 ));
2988 }
2989 let Some(value) = &capability.value else {
2990 return Err(GitError::InvalidFormat(
2991 "protocol v2 object-format capability is missing a value".into(),
2992 ));
2993 };
2994 format = Some(value.parse::<ObjectFormat>()?);
2995 }
2996 Ok(format.unwrap_or(ObjectFormat::Sha1))
2997}
2998
2999pub fn validate_protocol_v2_command_request_capabilities(
3000 handshake: &TransportHandshake,
3001 request: &ProtocolV2CommandRequest,
3002) -> Result<()> {
3003 if handshake.protocol != ProtocolVersion::V2 {
3004 return Err(GitError::InvalidFormat(
3005 "protocol v2 command validation requires a v2 handshake".into(),
3006 ));
3007 }
3008 let advertised =
3009 protocol_v2_capability(&handshake.capabilities, &request.command).ok_or_else(|| {
3010 GitError::InvalidFormat(format!("unadvertised command {}", request.command))
3011 })?;
3012 if advertised.name.is_empty() {
3013 return Err(GitError::InvalidFormat(
3014 "advertised command capability is empty".into(),
3015 ));
3016 }
3017 parse_protocol_v2_command_options(&request.capabilities)?;
3018
3019 for capability in &request.capabilities {
3020 let advertised = protocol_v2_capability(&handshake.capabilities, &capability.name)
3021 .ok_or_else(|| {
3022 GitError::InvalidFormat(format!(
3023 "unadvertised protocol v2 capability {}",
3024 capability.name
3025 ))
3026 })?;
3027 if capability.name == "object-format" {
3028 validate_protocol_v2_object_format_request(advertised, capability)?;
3029 }
3030 }
3031 Ok(())
3032}
3033
3034pub fn parse_protocol_v2_command_options(
3035 capabilities: &[Capability],
3036) -> Result<ProtocolV2CommandOptions> {
3037 let mut out = ProtocolV2CommandOptions::default();
3038 for capability in capabilities {
3039 match capability.name.as_str() {
3040 "agent" => {
3041 if out.agent.is_some() {
3042 return Err(GitError::InvalidFormat(
3043 "protocol v2 command has duplicate agent capabilities".into(),
3044 ));
3045 }
3046 let Some(value) = &capability.value else {
3047 return Err(GitError::InvalidFormat(
3048 "protocol v2 agent capability is missing a value".into(),
3049 ));
3050 };
3051 validate_protocol_v2_capability_value(value)?;
3052 out.agent = Some(value.clone());
3053 }
3054 "object-format" => {
3055 if out.object_format.is_some() {
3056 return Err(GitError::InvalidFormat(
3057 "protocol v2 command has duplicate object-format capabilities".into(),
3058 ));
3059 }
3060 let Some(value) = &capability.value else {
3061 return Err(GitError::InvalidFormat(
3062 "protocol v2 object-format capability is missing a value".into(),
3063 ));
3064 };
3065 out.object_format = Some(value.parse::<ObjectFormat>()?);
3066 }
3067 "server-option" => {
3068 let Some(value) = &capability.value else {
3069 return Err(GitError::InvalidFormat(
3070 "protocol v2 server-option capability is missing a value".into(),
3071 ));
3072 };
3073 validate_protocol_v2_capability_value(value)?;
3074 out.server_options.push(value.clone());
3075 }
3076 _ => out.extra.push(capability.clone()),
3077 }
3078 }
3079 Ok(out)
3080}
3081
3082pub fn encode_protocol_v2_command_options(
3083 options: &ProtocolV2CommandOptions,
3084) -> Result<Vec<Capability>> {
3085 let mut capabilities = Vec::new();
3086 if let Some(agent) = &options.agent {
3087 validate_protocol_v2_capability_value(agent)?;
3088 capabilities.push(Capability {
3089 name: "agent".into(),
3090 value: Some(agent.clone()),
3091 });
3092 }
3093 if let Some(format) = options.object_format {
3094 capabilities.push(Capability {
3095 name: "object-format".into(),
3096 value: Some(format.name().into()),
3097 });
3098 }
3099 for option in &options.server_options {
3100 validate_protocol_v2_capability_value(option)?;
3101 capabilities.push(Capability {
3102 name: "server-option".into(),
3103 value: Some(option.clone()),
3104 });
3105 }
3106 for capability in &options.extra {
3107 if matches!(
3108 capability.name.as_str(),
3109 "agent" | "object-format" | "server-option"
3110 ) {
3111 return Err(GitError::InvalidFormat(format!(
3112 "protocol v2 extra capability duplicates known capability {}",
3113 capability.name
3114 )));
3115 }
3116 encode_protocol_v2_capability(capability)?;
3117 capabilities.push(capability.clone());
3118 }
3119 Ok(capabilities)
3120}
3121
3122pub fn parse_protocol_v2_ls_refs_features(
3123 capabilities: &[Capability],
3124) -> Result<Option<ProtocolV2LsRefsFeatures>> {
3125 let mut ls_refs = None;
3126 for capability in capabilities {
3127 if capability.name != "ls-refs" {
3128 continue;
3129 }
3130 if ls_refs.is_some() {
3131 return Err(GitError::InvalidFormat(
3132 "protocol v2 has duplicate ls-refs capabilities".into(),
3133 ));
3134 }
3135 ls_refs = Some(parse_protocol_v2_ls_refs_feature_value(
3136 capability.value.as_deref(),
3137 )?);
3138 }
3139 Ok(ls_refs)
3140}
3141
3142pub fn encode_protocol_v2_ls_refs_capability(
3143 features: &ProtocolV2LsRefsFeatures,
3144) -> Result<Capability> {
3145 let mut values = Vec::new();
3146 if features.unborn {
3147 values.push("unborn".to_string());
3148 }
3149 for feature in &features.unknown {
3150 validate_protocol_v2_token("ls-refs feature", feature)?;
3151 if feature == "unborn" {
3152 return Err(GitError::InvalidFormat(
3153 "ls-refs unknown features must not duplicate known feature unborn".into(),
3154 ));
3155 }
3156 values.push(feature.clone());
3157 }
3158 Ok(Capability {
3159 name: "ls-refs".into(),
3160 value: (!values.is_empty()).then(|| values.join(" ")),
3161 })
3162}
3163
3164pub fn validate_protocol_v2_ls_refs_request_features(
3165 features: &ProtocolV2LsRefsFeatures,
3166 request: &ProtocolV2LsRefsRequest,
3167) -> Result<()> {
3168 if request.unborn && !features.unborn {
3169 return Err(GitError::InvalidFormat(
3170 "ls-refs request uses unborn without advertised unborn feature".into(),
3171 ));
3172 }
3173 Ok(())
3174}
3175
3176pub fn validate_protocol_v2_ls_refs_command_request(
3177 handshake: &TransportHandshake,
3178 request: &ProtocolV2CommandRequest,
3179) -> Result<ProtocolV2LsRefsRequest> {
3180 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3181 let ls_refs = ProtocolV2LsRefsRequest::from_command_request(request)?;
3182 let features = parse_protocol_v2_ls_refs_features(&handshake.capabilities)?
3183 .ok_or_else(|| GitError::InvalidFormat("ls-refs command was not advertised".into()))?;
3184 validate_protocol_v2_ls_refs_request_features(&features, &ls_refs)?;
3185 Ok(ls_refs)
3186}
3187
3188pub fn parse_protocol_v2_fetch_features(
3189 capabilities: &[Capability],
3190) -> Result<Option<ProtocolV2FetchFeatures>> {
3191 let mut fetch = None;
3192 for capability in capabilities {
3193 if capability.name != "fetch" {
3194 continue;
3195 }
3196 if fetch.is_some() {
3197 return Err(GitError::InvalidFormat(
3198 "protocol v2 has duplicate fetch capabilities".into(),
3199 ));
3200 }
3201 fetch = Some(parse_protocol_v2_fetch_feature_value(
3202 capability.value.as_deref(),
3203 )?);
3204 }
3205 Ok(fetch)
3206}
3207
3208pub fn encode_protocol_v2_fetch_capability(
3209 features: &ProtocolV2FetchFeatures,
3210) -> Result<Capability> {
3211 let mut values = Vec::new();
3212 if features.shallow {
3213 values.push("shallow".to_string());
3214 }
3215 if features.wait_for_done {
3216 values.push("wait-for-done".to_string());
3217 }
3218 if features.filter {
3219 values.push("filter".to_string());
3220 }
3221 if features.ref_in_want {
3222 values.push("ref-in-want".to_string());
3223 }
3224 if features.sideband_all {
3225 values.push("sideband-all".to_string());
3226 }
3227 if features.packfile_uris {
3228 values.push("packfile-uris".to_string());
3229 }
3230 for feature in &features.unknown {
3231 validate_protocol_v2_token("fetch feature", feature)?;
3232 if matches!(
3233 feature.as_str(),
3234 "shallow"
3235 | "wait-for-done"
3236 | "filter"
3237 | "ref-in-want"
3238 | "sideband-all"
3239 | "packfile-uris"
3240 ) {
3241 return Err(GitError::InvalidFormat(format!(
3242 "fetch unknown features must not duplicate known feature {feature}"
3243 )));
3244 }
3245 values.push(feature.clone());
3246 }
3247 Ok(Capability {
3248 name: "fetch".into(),
3249 value: (!values.is_empty()).then(|| values.join(" ")),
3250 })
3251}
3252
3253pub fn validate_protocol_v2_fetch_request_features(
3254 features: &ProtocolV2FetchFeatures,
3255 request: &ProtocolV2FetchRequest,
3256) -> Result<()> {
3257 if !features.shallow
3258 && (!request.shallow.is_empty()
3259 || request.deepen.is_some()
3260 || request.deepen_since.is_some()
3261 || !request.deepen_not.is_empty()
3262 || request.deepen_relative)
3263 {
3264 return Err(GitError::InvalidFormat(
3265 "fetch request uses shallow/deepen arguments without advertised shallow feature".into(),
3266 ));
3267 }
3268 if !features.filter && request.filter.is_some() {
3269 return Err(GitError::InvalidFormat(
3270 "fetch request uses filter without advertised filter feature".into(),
3271 ));
3272 }
3273 if !features.ref_in_want && !request.want_refs.is_empty() {
3274 return Err(GitError::InvalidFormat(
3275 "fetch request uses want-ref without advertised ref-in-want feature".into(),
3276 ));
3277 }
3278 if !features.sideband_all && request.sideband_all {
3279 return Err(GitError::InvalidFormat(
3280 "fetch request uses sideband-all without advertised sideband-all feature".into(),
3281 ));
3282 }
3283 if !features.packfile_uris && request.packfile_uris.is_some() {
3284 return Err(GitError::InvalidFormat(
3285 "fetch request uses packfile-uris without advertised packfile-uris feature".into(),
3286 ));
3287 }
3288 if !features.wait_for_done && request.wait_for_done {
3289 return Err(GitError::InvalidFormat(
3290 "fetch request uses wait-for-done without advertised wait-for-done feature".into(),
3291 ));
3292 }
3293 Ok(())
3294}
3295
3296pub fn validate_protocol_v2_fetch_command_request(
3297 handshake: &TransportHandshake,
3298 format: ObjectFormat,
3299 request: &ProtocolV2CommandRequest,
3300) -> Result<ProtocolV2FetchRequest> {
3301 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3302 let fetch = ProtocolV2FetchRequest::from_command_request(format, request)?;
3303 let features = parse_protocol_v2_fetch_features(&handshake.capabilities)?
3304 .ok_or_else(|| GitError::InvalidFormat("fetch command was not advertised".into()))?;
3305 validate_protocol_v2_fetch_request_features(&features, &fetch)?;
3306 Ok(fetch)
3307}
3308
3309pub fn validate_protocol_v2_object_info_command_request(
3310 handshake: &TransportHandshake,
3311 format: ObjectFormat,
3312 request: &ProtocolV2CommandRequest,
3313) -> Result<ProtocolV2ObjectInfoRequest> {
3314 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3315 let object_info = ProtocolV2ObjectInfoRequest::from_command_request(format, request)?;
3316 protocol_v2_capability(&handshake.capabilities, "object-info")
3317 .ok_or_else(|| GitError::InvalidFormat("object-info command was not advertised".into()))?;
3318 Ok(object_info)
3319}
3320
3321pub fn classify_protocol_v2_command_request(
3322 handshake: &TransportHandshake,
3323 format: ObjectFormat,
3324 request: &ProtocolV2CommandRequest,
3325) -> Result<ProtocolV2Command> {
3326 match request.command.as_str() {
3327 "ls-refs" => validate_protocol_v2_ls_refs_command_request(handshake, request)
3328 .map(ProtocolV2Command::LsRefs),
3329 "fetch" => validate_protocol_v2_fetch_command_request(handshake, format, request)
3330 .map(ProtocolV2Command::Fetch),
3331 "object-info" => {
3332 validate_protocol_v2_object_info_command_request(handshake, format, request)
3333 .map(ProtocolV2Command::ObjectInfo)
3334 }
3335 _ => {
3336 validate_protocol_v2_command_request_capabilities(handshake, request)?;
3337 Ok(ProtocolV2Command::Unknown(request.clone()))
3338 }
3339 }
3340}
3341
3342pub fn classify_protocol_v2_request(
3343 handshake: &TransportHandshake,
3344 format: ObjectFormat,
3345 request: &ProtocolV2Request,
3346) -> Result<ProtocolV2SessionRequest> {
3347 match request {
3348 ProtocolV2Request::Command(command) => {
3349 classify_protocol_v2_command_request(handshake, format, command)
3350 .map(ProtocolV2SessionRequest::Command)
3351 }
3352 ProtocolV2Request::Done => Ok(ProtocolV2SessionRequest::Done),
3353 }
3354}
3355
3356pub fn read_protocol_v2_session_request(
3357 handshake: &TransportHandshake,
3358 format: ObjectFormat,
3359 reader: &mut impl Read,
3360) -> Result<ProtocolV2SessionRequest> {
3361 let request = read_protocol_v2_request(reader)?;
3362 classify_protocol_v2_request(handshake, format, &request)
3363}
3364
3365fn protocol_v2_capability<'a>(
3366 capabilities: &'a [Capability],
3367 name: &str,
3368) -> Option<&'a Capability> {
3369 capabilities
3370 .iter()
3371 .find(|capability| capability.name == name)
3372}
3373
3374fn validate_protocol_v2_object_format_request(
3375 advertised: &Capability,
3376 requested: &Capability,
3377) -> Result<()> {
3378 let Some(advertised) = &advertised.value else {
3379 return Err(GitError::InvalidFormat(
3380 "advertised object-format capability is missing a value".into(),
3381 ));
3382 };
3383 let Some(requested) = &requested.value else {
3384 return Err(GitError::InvalidFormat(
3385 "requested object-format capability is missing a value".into(),
3386 ));
3387 };
3388 if advertised != requested {
3389 return Err(GitError::InvalidFormat(format!(
3390 "requested object-format {requested} does not match advertised {advertised}"
3391 )));
3392 }
3393 Ok(())
3394}
3395
3396fn parse_protocol_v2_ls_refs_feature_value(
3397 value: Option<&str>,
3398) -> Result<ProtocolV2LsRefsFeatures> {
3399 let mut out = ProtocolV2LsRefsFeatures::default();
3400 let Some(value) = value else {
3401 return Ok(out);
3402 };
3403 if value.is_empty() {
3404 return Err(GitError::InvalidFormat(
3405 "protocol v2 ls-refs capability value is empty".into(),
3406 ));
3407 }
3408 for feature in value.split(' ') {
3409 validate_protocol_v2_token("ls-refs feature", feature)?;
3410 match feature {
3411 "unborn" => out.unborn = true,
3412 other => out.unknown.push(other.to_string()),
3413 }
3414 }
3415 Ok(out)
3416}
3417
3418fn parse_protocol_v2_fetch_feature_value(value: Option<&str>) -> Result<ProtocolV2FetchFeatures> {
3419 let mut out = ProtocolV2FetchFeatures::default();
3420 let Some(value) = value else {
3421 return Ok(out);
3422 };
3423 if value.is_empty() {
3424 return Err(GitError::InvalidFormat(
3425 "protocol v2 fetch capability value is empty".into(),
3426 ));
3427 }
3428 for feature in value.split(' ') {
3429 validate_protocol_v2_token("fetch feature", feature)?;
3430 match feature {
3431 "shallow" => out.shallow = true,
3432 "wait-for-done" => out.wait_for_done = true,
3433 "filter" => out.filter = true,
3434 "ref-in-want" => out.ref_in_want = true,
3435 "sideband-all" => out.sideband_all = true,
3436 "packfile-uris" => out.packfile_uris = true,
3437 other => out.unknown.push(other.to_string()),
3438 }
3439 }
3440 Ok(out)
3441}
3442
3443pub fn parse_capabilities(input: &[u8]) -> Result<Vec<Capability>> {
3444 let input = trim_trailing_lf(input);
3445 if input.is_empty() {
3446 return Ok(Vec::new());
3447 }
3448 let text =
3449 std::str::from_utf8(input).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3450 text.split(' ')
3451 .map(parse_capability_token)
3452 .collect::<Result<Vec<_>>>()
3453}
3454
3455pub fn encode_capabilities(capabilities: &[Capability]) -> Result<Vec<u8>> {
3456 let mut out = Vec::new();
3457 for (idx, capability) in capabilities.iter().enumerate() {
3458 validate_capability_field("capability name", &capability.name)?;
3459 if idx != 0 {
3460 out.push(b' ');
3461 }
3462 out.extend_from_slice(capability.name.as_bytes());
3463 if let Some(value) = &capability.value {
3464 validate_capability_field("capability value", value)?;
3465 out.push(b'=');
3466 out.extend_from_slice(value.as_bytes());
3467 }
3468 }
3469 Ok(out)
3470}
3471
3472pub fn parse_ref_advertisement(format: ObjectFormat, payload: &[u8]) -> Result<RefAdvertisement> {
3473 let payload = trim_trailing_lf(payload);
3474 let (reference, capabilities) = match payload.iter().position(|byte| *byte == 0) {
3475 Some(idx) => (&payload[..idx], parse_capabilities(&payload[idx + 1..])?),
3476 None => (payload, Vec::new()),
3477 };
3478 let text =
3479 std::str::from_utf8(reference).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
3480 let (oid, name) = text
3481 .split_once(' ')
3482 .ok_or_else(|| GitError::InvalidFormat("advertised ref is missing name".into()))?;
3483 if name.is_empty() {
3484 return Err(GitError::InvalidFormat(
3485 "advertised ref name is empty".into(),
3486 ));
3487 }
3488 Ok(RefAdvertisement {
3489 oid: ObjectId::from_hex(format, oid)?,
3490 name: name.to_string(),
3491 capabilities,
3492 })
3493}
3494
3495pub fn encode_ref_advertisement(advertisement: &RefAdvertisement) -> Result<Vec<u8>> {
3496 validate_protocol_v2_token("advertised ref name", &advertisement.name)?;
3497 let mut out = advertisement.oid.to_string().into_bytes();
3498 out.push(b' ');
3499 out.extend_from_slice(advertisement.name.as_bytes());
3500 if !advertisement.capabilities.is_empty() {
3501 out.push(0);
3502 out.extend_from_slice(&encode_capabilities(&advertisement.capabilities)?);
3503 }
3504 out.push(b'\n');
3505 Ok(out)
3506}
3507
3508pub fn parse_ref_advertisements(
3509 format: ObjectFormat,
3510 frames: &[PktLineFrame],
3511) -> Result<Vec<RefAdvertisement>> {
3512 Ok(parse_ref_advertisement_set(format, frames)?.refs)
3513}
3514
3515pub fn parse_ref_advertisement_set(
3516 format: ObjectFormat,
3517 frames: &[PktLineFrame],
3518) -> Result<RefAdvertisementSet> {
3519 let mut set = RefAdvertisementSet {
3520 protocol: ProtocolVersion::V0,
3521 refs: Vec::new(),
3522 shallow: Vec::new(),
3523 };
3524 let mut saw_flush = false;
3525 let mut in_shallow = false;
3526 for (idx, frame) in frames.iter().enumerate() {
3527 match frame {
3528 PktLineFrame::Data(payload) if !saw_flush => {
3529 let trimmed = trim_trailing_lf(payload);
3530 if trimmed == b"version 1" {
3531 if idx != 0 {
3532 return Err(GitError::InvalidFormat(
3533 "advertised ref protocol version must be the first line".into(),
3534 ));
3535 }
3536 set.protocol = ProtocolVersion::V1;
3537 continue;
3538 }
3539 if trimmed.starts_with(b"version ") {
3540 return Err(GitError::InvalidFormat(
3541 "unsupported advertised ref protocol version".into(),
3542 ));
3543 }
3544 if trimmed.starts_with(b"shallow ") {
3545 if set.refs.is_empty() {
3546 return Err(GitError::InvalidFormat(
3547 "advertised shallow refs must follow advertised refs".into(),
3548 ));
3549 }
3550 let text = parse_protocol_v2_line_text("advertised shallow ref", payload)?;
3551 set.shallow.push(parse_oid_argument(
3552 format,
3553 "advertised shallow ref",
3554 text,
3555 "shallow ",
3556 )?);
3557 in_shallow = true;
3558 continue;
3559 }
3560 if in_shallow {
3561 return Err(GitError::InvalidFormat(
3562 "advertised refs must not follow shallow refs".into(),
3563 ));
3564 }
3565 let advertisement = parse_ref_advertisement(format, payload)?;
3566 if !set.refs.is_empty() && !advertisement.capabilities.is_empty() {
3567 return Err(GitError::InvalidFormat(
3568 "advertised ref capabilities must appear on the first ref".into(),
3569 ));
3570 }
3571 set.refs.push(advertisement);
3572 }
3573 PktLineFrame::Data(_) => {
3574 return Err(GitError::InvalidFormat(
3575 "advertised ref stream has data after flush".into(),
3576 ));
3577 }
3578 PktLineFrame::Flush => {
3579 saw_flush = true;
3580 if idx + 1 != frames.len() {
3581 return Err(GitError::InvalidFormat(
3582 "advertised ref stream has frames after flush".into(),
3583 ));
3584 }
3585 }
3586 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3587 return Err(GitError::InvalidFormat(
3588 "advertised ref stream contains a non-flush control packet".into(),
3589 ));
3590 }
3591 }
3592 }
3593 if !saw_flush {
3594 return Err(GitError::InvalidFormat(
3595 "advertised ref stream missing flush".into(),
3596 ));
3597 }
3598 Ok(set)
3599}
3600
3601pub fn encode_ref_advertisements(advertisements: &[RefAdvertisement]) -> Result<Vec<PktLineFrame>> {
3602 encode_ref_advertisement_set(&RefAdvertisementSet {
3603 protocol: ProtocolVersion::V0,
3604 refs: advertisements.to_vec(),
3605 shallow: Vec::new(),
3606 })
3607}
3608
3609pub fn encode_ref_advertisement_set(set: &RefAdvertisementSet) -> Result<Vec<PktLineFrame>> {
3610 let mut frames = Vec::new();
3611 match set.protocol {
3612 ProtocolVersion::V0 => {}
3613 ProtocolVersion::V1 => frames.push(PktLineFrame::data(line_from_str("version 1"))?),
3614 ProtocolVersion::V2 => {
3615 return Err(GitError::InvalidFormat(
3616 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3617 ));
3618 }
3619 }
3620 if set.refs.is_empty() && !set.shallow.is_empty() {
3621 return Err(GitError::InvalidFormat(
3622 "advertised shallow refs require advertised refs".into(),
3623 ));
3624 }
3625 for (idx, advertisement) in set.refs.iter().enumerate() {
3626 if idx != 0 && !advertisement.capabilities.is_empty() {
3627 return Err(GitError::InvalidFormat(
3628 "advertised ref capabilities must appear on the first ref".into(),
3629 ));
3630 }
3631 frames.push(PktLineFrame::data(encode_ref_advertisement(
3632 advertisement,
3633 )?)?);
3634 }
3635 for oid in &set.shallow {
3636 frames.push(PktLineFrame::data(line_from_str(&format!(
3637 "shallow {oid}"
3638 )))?);
3639 }
3640 frames.push(PktLineFrame::Flush);
3641 Ok(frames)
3642}
3643
3644pub fn read_ref_advertisements(
3645 format: ObjectFormat,
3646 reader: &mut impl Read,
3647) -> Result<Vec<RefAdvertisement>> {
3648 let frames = read_pkt_line_frames_until_flush(reader)?;
3649 parse_ref_advertisements(format, &frames)
3650}
3651
3652pub fn read_ref_advertisement_set(
3653 format: ObjectFormat,
3654 reader: &mut impl Read,
3655) -> Result<RefAdvertisementSet> {
3656 let frames = read_pkt_line_frames_until_flush(reader)?;
3657 parse_ref_advertisement_set(format, &frames)
3658}
3659
3660pub fn write_ref_advertisements(
3661 writer: &mut impl Write,
3662 advertisements: &[RefAdvertisement],
3663) -> Result<()> {
3664 write_ref_advertisement_stream(writer, ProtocolVersion::V0, advertisements, &[])
3665}
3666
3667pub fn write_ref_advertisement_set(
3668 writer: &mut impl Write,
3669 set: &RefAdvertisementSet,
3670) -> Result<()> {
3671 write_ref_advertisement_stream(writer, set.protocol, &set.refs, &set.shallow)
3672}
3673
3674fn write_ref_advertisement_stream(
3675 writer: &mut impl Write,
3676 protocol: ProtocolVersion,
3677 refs: &[RefAdvertisement],
3678 shallow: &[ObjectId],
3679) -> Result<()> {
3680 match protocol {
3681 ProtocolVersion::V0 => {}
3682 ProtocolVersion::V1 => write_pkt_line_payload(writer, b"version 1\n")?,
3683 ProtocolVersion::V2 => {
3684 return Err(GitError::InvalidFormat(
3685 "protocol v2 does not use v0/v1 advertised-ref streams".into(),
3686 ));
3687 }
3688 }
3689 if refs.is_empty() && !shallow.is_empty() {
3690 return Err(GitError::InvalidFormat(
3691 "advertised shallow refs require advertised refs".into(),
3692 ));
3693 }
3694 for (idx, advertisement) in refs.iter().enumerate() {
3695 if idx != 0 && !advertisement.capabilities.is_empty() {
3696 return Err(GitError::InvalidFormat(
3697 "advertised ref capabilities must appear on the first ref".into(),
3698 ));
3699 }
3700 write_pkt_line_payload(writer, &encode_ref_advertisement(advertisement)?)?;
3701 }
3702 for oid in shallow {
3703 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
3704 }
3705 writer.write_all(b"0000")?;
3706 Ok(())
3707}
3708
3709pub fn parse_dumb_http_info_refs(
3710 format: ObjectFormat,
3711 input: &[u8],
3712) -> Result<Vec<DumbHttpRefRecord>> {
3713 if input.is_empty() {
3714 return Ok(Vec::new());
3715 }
3716 input
3717 .split_inclusive(|byte| *byte == b'\n')
3718 .map(|line| parse_dumb_http_info_ref_record(format, line))
3719 .collect()
3720}
3721
3722pub fn encode_dumb_http_info_refs(records: &[DumbHttpRefRecord]) -> Result<Vec<u8>> {
3723 let mut out = Vec::new();
3724 for record in records {
3725 validate_dumb_http_ref_name(&record.name)?;
3726 out.extend_from_slice(record.oid.to_string().as_bytes());
3727 out.push(b'\t');
3728 out.extend_from_slice(record.name.as_bytes());
3729 if record.peeled {
3730 out.extend_from_slice(b"^{}");
3731 }
3732 out.push(b'\n');
3733 }
3734 Ok(out)
3735}
3736
3737pub fn read_dumb_http_info_refs(
3738 format: ObjectFormat,
3739 reader: &mut impl Read,
3740) -> Result<Vec<DumbHttpRefRecord>> {
3741 let mut input = Vec::new();
3742 reader.read_to_end(&mut input)?;
3743 parse_dumb_http_info_refs(format, &input)
3744}
3745
3746pub fn write_dumb_http_info_refs(
3747 writer: &mut impl Write,
3748 records: &[DumbHttpRefRecord],
3749) -> Result<()> {
3750 for record in records {
3751 validate_dumb_http_ref_name(&record.name)?;
3752 writer.write_all(record.oid.to_string().as_bytes())?;
3753 writer.write_all(b"\t")?;
3754 writer.write_all(record.name.as_bytes())?;
3755 if record.peeled {
3756 writer.write_all(b"^{}")?;
3757 }
3758 writer.write_all(b"\n")?;
3759 }
3760 Ok(())
3761}
3762
3763pub fn parse_dumb_http_alternates(input: &[u8]) -> Result<Vec<String>> {
3764 if input.is_empty() {
3765 return Ok(Vec::new());
3766 }
3767 input
3768 .split_inclusive(|byte| *byte == b'\n')
3769 .map(parse_dumb_http_alternate)
3770 .collect()
3771}
3772
3773pub fn encode_dumb_http_alternates(alternates: &[String]) -> Result<Vec<u8>> {
3774 let mut out = Vec::new();
3775 for alternate in alternates {
3776 validate_dumb_http_alternate(alternate)?;
3777 out.extend_from_slice(alternate.as_bytes());
3778 out.push(b'\n');
3779 }
3780 Ok(out)
3781}
3782
3783pub fn read_dumb_http_alternates(reader: &mut impl Read) -> Result<Vec<String>> {
3784 let mut input = Vec::new();
3785 reader.read_to_end(&mut input)?;
3786 parse_dumb_http_alternates(&input)
3787}
3788
3789pub fn write_dumb_http_alternates(writer: &mut impl Write, alternates: &[String]) -> Result<()> {
3790 for alternate in alternates {
3791 validate_dumb_http_alternate(alternate)?;
3792 writer.write_all(alternate.as_bytes())?;
3793 writer.write_all(b"\n")?;
3794 }
3795 Ok(())
3796}
3797
3798pub fn parse_dumb_http_packs(
3799 format: ObjectFormat,
3800 input: &[u8],
3801) -> Result<Vec<DumbHttpPackRecord>> {
3802 if input.is_empty() {
3803 return Ok(Vec::new());
3804 }
3805 input
3806 .split_inclusive(|byte| *byte == b'\n')
3807 .map(|line| parse_dumb_http_pack_record(format, line))
3808 .collect()
3809}
3810
3811pub fn encode_dumb_http_packs(records: &[DumbHttpPackRecord]) -> Result<Vec<u8>> {
3812 let mut out = Vec::new();
3813 for record in records {
3814 out.extend_from_slice(format!("P pack-{}.pack\n", record.hash).as_bytes());
3815 }
3816 Ok(out)
3817}
3818
3819pub fn read_dumb_http_packs(
3820 format: ObjectFormat,
3821 reader: &mut impl Read,
3822) -> Result<Vec<DumbHttpPackRecord>> {
3823 let mut input = Vec::new();
3824 reader.read_to_end(&mut input)?;
3825 parse_dumb_http_packs(format, &input)
3826}
3827
3828pub fn write_dumb_http_packs(
3829 writer: &mut impl Write,
3830 records: &[DumbHttpPackRecord],
3831) -> Result<()> {
3832 for record in records {
3833 writer.write_all(format!("P pack-{}.pack\n", record.hash).as_bytes())?;
3834 }
3835 Ok(())
3836}
3837
3838pub fn parse_upload_pack_request(
3839 format: ObjectFormat,
3840 frames: &[PktLineFrame],
3841) -> Result<Option<UploadPackRequest>> {
3842 if matches!(frames, [PktLineFrame::Flush]) {
3843 return Ok(None);
3844 }
3845
3846 let mut request = UploadPackRequest::default();
3847 let mut in_options = false;
3848 let mut saw_flush = false;
3849 for (idx, frame) in frames.iter().enumerate() {
3850 match frame {
3851 PktLineFrame::Data(payload) if !saw_flush => {
3852 let text = parse_protocol_v2_line_text("upload-pack request line", payload)?;
3853 if let Some(value) = text.strip_prefix("want ") {
3854 if in_options {
3855 return Err(GitError::InvalidFormat(
3856 "upload-pack request has want after options".into(),
3857 ));
3858 }
3859 let (oid, capabilities) = if request.wants.is_empty() {
3860 value
3861 .split_once(' ')
3862 .map_or((value, None), |(oid, caps)| (oid, Some(caps.as_bytes())))
3863 } else {
3864 if value.contains(' ') {
3865 return Err(GitError::InvalidFormat(
3866 "additional upload-pack want has capabilities".into(),
3867 ));
3868 }
3869 (value, None)
3870 };
3871 validate_protocol_v2_token("upload-pack want", oid)?;
3872 request.wants.push(ObjectId::from_hex(format, oid)?);
3873 if let Some(capabilities) = capabilities {
3874 request.capabilities = parse_capabilities(capabilities)?;
3875 }
3876 continue;
3877 }
3878
3879 if request.wants.is_empty() {
3880 return Err(GitError::InvalidFormat(
3881 "upload-pack request must start with want".into(),
3882 ));
3883 }
3884 in_options = true;
3885 if text.starts_with("shallow ") {
3886 request.shallow.push(parse_oid_argument(
3887 format,
3888 "upload-pack shallow",
3889 text,
3890 "shallow ",
3891 )?);
3892 } else if text.starts_with("deepen ") {
3893 if request.deepen.is_some() {
3894 return Err(GitError::InvalidFormat(
3895 "upload-pack request has duplicate deepen".into(),
3896 ));
3897 }
3898 request.deepen =
3899 Some(parse_u32_argument("upload-pack deepen", text, "deepen ")?);
3900 } else if text.starts_with("deepen-since ") {
3901 if request.deepen_since.is_some() {
3902 return Err(GitError::InvalidFormat(
3903 "upload-pack request has duplicate deepen-since".into(),
3904 ));
3905 }
3906 request.deepen_since = Some(parse_u64_argument(
3907 "upload-pack deepen-since",
3908 text,
3909 "deepen-since ",
3910 )?);
3911 } else if let Some(name) = text.strip_prefix("deepen-not ") {
3912 validate_protocol_v2_token("upload-pack deepen-not", name)?;
3913 request.deepen_not.push(name.to_string());
3914 } else if let Some(filter) = text.strip_prefix("filter ") {
3915 if request.filter.is_some() {
3916 return Err(GitError::InvalidFormat(
3917 "upload-pack request has duplicate filter".into(),
3918 ));
3919 }
3920 validate_protocol_v2_token("upload-pack filter", filter)?;
3921 request.filter = Some(filter.to_string());
3922 } else {
3923 return Err(GitError::InvalidFormat(format!(
3924 "unsupported upload-pack request line {text}"
3925 )));
3926 }
3927 }
3928 PktLineFrame::Data(_) => {
3929 return Err(GitError::InvalidFormat(
3930 "upload-pack request has data after flush".into(),
3931 ));
3932 }
3933 PktLineFrame::Flush => {
3934 saw_flush = true;
3935 if idx + 1 != frames.len() {
3936 return Err(GitError::InvalidFormat(
3937 "upload-pack request has frames after flush".into(),
3938 ));
3939 }
3940 }
3941 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
3942 return Err(GitError::InvalidFormat(
3943 "upload-pack request contains a non-flush control packet".into(),
3944 ));
3945 }
3946 }
3947 }
3948 if !saw_flush {
3949 return Err(GitError::InvalidFormat(
3950 "upload-pack request missing flush".into(),
3951 ));
3952 }
3953 if request.wants.is_empty() {
3954 return Err(GitError::InvalidFormat(
3955 "upload-pack request missing want".into(),
3956 ));
3957 }
3958 Ok(Some(request))
3959}
3960
3961pub fn encode_upload_pack_request(
3962 request: Option<&UploadPackRequest>,
3963) -> Result<Vec<PktLineFrame>> {
3964 let Some(request) = request else {
3965 return Ok(vec![PktLineFrame::Flush]);
3966 };
3967 if request.wants.is_empty() {
3968 return Err(GitError::InvalidFormat(
3969 "upload-pack request missing want".into(),
3970 ));
3971 }
3972
3973 let mut frames = Vec::new();
3974 for (idx, oid) in request.wants.iter().enumerate() {
3975 let mut line = format!("want {oid}");
3976 if idx == 0 && !request.capabilities.is_empty() {
3977 line.push(' ');
3978 line.push_str(
3979 &String::from_utf8(encode_capabilities(&request.capabilities)?)
3980 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
3981 );
3982 }
3983 frames.push(PktLineFrame::data(line_from_str(&line))?);
3984 }
3985 for oid in &request.shallow {
3986 frames.push(PktLineFrame::data(line_from_str(&format!(
3987 "shallow {oid}"
3988 )))?);
3989 }
3990 if let Some(deepen) = request.deepen {
3991 if deepen == 0 {
3992 return Err(GitError::InvalidFormat(
3993 "upload-pack deepen must be positive".into(),
3994 ));
3995 }
3996 frames.push(PktLineFrame::data(line_from_str(&format!(
3997 "deepen {deepen}"
3998 )))?);
3999 }
4000 if let Some(deepen_since) = request.deepen_since {
4001 frames.push(PktLineFrame::data(line_from_str(&format!(
4002 "deepen-since {deepen_since}"
4003 )))?);
4004 }
4005 for name in &request.deepen_not {
4006 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4007 frames.push(PktLineFrame::data(line_from_str(&format!(
4008 "deepen-not {name}"
4009 )))?);
4010 }
4011 if let Some(filter) = &request.filter {
4012 validate_protocol_v2_token("upload-pack filter", filter)?;
4013 frames.push(PktLineFrame::data(line_from_str(&format!(
4014 "filter {filter}"
4015 )))?);
4016 }
4017 frames.push(PktLineFrame::Flush);
4018 Ok(frames)
4019}
4020
4021pub fn read_upload_pack_request(
4022 format: ObjectFormat,
4023 reader: &mut impl Read,
4024) -> Result<Option<UploadPackRequest>> {
4025 let frames = read_pkt_line_frames_until_flush(reader)?;
4026 parse_upload_pack_request(format, &frames)
4027}
4028
4029pub fn write_upload_pack_request(
4030 writer: &mut impl Write,
4031 request: Option<&UploadPackRequest>,
4032) -> Result<()> {
4033 let Some(request) = request else {
4034 writer.write_all(b"0000")?;
4035 return Ok(());
4036 };
4037 if request.wants.is_empty() {
4038 return Err(GitError::InvalidFormat(
4039 "upload-pack request missing want".into(),
4040 ));
4041 }
4042
4043 for (idx, oid) in request.wants.iter().enumerate() {
4044 let mut line = format!("want {oid}");
4045 if idx == 0 && !request.capabilities.is_empty() {
4046 line.push(' ');
4047 line.push_str(
4048 &String::from_utf8(encode_capabilities(&request.capabilities)?)
4049 .map_err(|err| GitError::InvalidFormat(err.to_string()))?,
4050 );
4051 }
4052 write_pkt_line_payload(writer, &line_from_str(&line))?;
4053 }
4054 for oid in &request.shallow {
4055 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
4056 }
4057 if let Some(deepen) = request.deepen {
4058 if deepen == 0 {
4059 return Err(GitError::InvalidFormat(
4060 "upload-pack deepen must be positive".into(),
4061 ));
4062 }
4063 write_pkt_line_payload(writer, &line_from_str(&format!("deepen {deepen}")))?;
4064 }
4065 if let Some(deepen_since) = request.deepen_since {
4066 write_pkt_line_payload(
4067 writer,
4068 &line_from_str(&format!("deepen-since {deepen_since}")),
4069 )?;
4070 }
4071 for name in &request.deepen_not {
4072 validate_protocol_v2_token("upload-pack deepen-not", name)?;
4073 write_pkt_line_payload(writer, &line_from_str(&format!("deepen-not {name}")))?;
4074 }
4075 if let Some(filter) = &request.filter {
4076 validate_protocol_v2_token("upload-pack filter", filter)?;
4077 write_pkt_line_payload(writer, &line_from_str(&format!("filter {filter}")))?;
4078 }
4079 writer.write_all(b"0000")?;
4080 Ok(())
4081}
4082
4083pub fn parse_upload_pack_features(capabilities: &[Capability]) -> Result<UploadPackFeatures> {
4084 let mut features = UploadPackFeatures::default();
4085 for capability in capabilities {
4086 match capability.name.as_str() {
4087 "multi_ack" => set_upload_pack_flag(&mut features.multi_ack, capability)?,
4088 "multi_ack_detailed" => {
4089 set_upload_pack_flag(&mut features.multi_ack_detailed, capability)?
4090 }
4091 "no-done" => set_upload_pack_flag(&mut features.no_done, capability)?,
4092 "thin-pack" => set_upload_pack_flag(&mut features.thin_pack, capability)?,
4093 "side-band" => set_upload_pack_flag(&mut features.side_band, capability)?,
4094 "side-band-64k" => set_upload_pack_flag(&mut features.side_band_64k, capability)?,
4095 "ofs-delta" => set_upload_pack_flag(&mut features.ofs_delta, capability)?,
4096 "shallow" => set_upload_pack_flag(&mut features.shallow, capability)?,
4097 "deepen-since" => set_upload_pack_flag(&mut features.deepen_since, capability)?,
4098 "deepen-not" => set_upload_pack_flag(&mut features.deepen_not, capability)?,
4099 "include-tag" => set_upload_pack_flag(&mut features.include_tag, capability)?,
4100 "no-progress" => set_upload_pack_flag(&mut features.no_progress, capability)?,
4101 "allow-tip-sha1-in-want" => {
4102 set_upload_pack_flag(&mut features.allow_tip_sha1_in_want, capability)?
4103 }
4104 "allow-reachable-sha1-in-want" => {
4105 set_upload_pack_flag(&mut features.allow_reachable_sha1_in_want, capability)?
4106 }
4107 "filter" => set_upload_pack_flag(&mut features.filter, capability)?,
4108 "agent" => {
4109 let Some(agent) = &capability.value else {
4110 return Err(GitError::InvalidFormat(
4111 "upload-pack agent capability is missing value".into(),
4112 ));
4113 };
4114 if features.agent.is_some() {
4115 return Err(GitError::InvalidFormat(
4116 "upload-pack has duplicate agent capability".into(),
4117 ));
4118 }
4119 validate_capability_field("upload-pack agent", agent)?;
4120 features.agent = Some(agent.clone());
4121 }
4122 "object-format" => {
4123 let Some(format) = &capability.value else {
4124 return Err(GitError::InvalidFormat(
4125 "upload-pack object-format capability is missing value".into(),
4126 ));
4127 };
4128 if features.object_format.is_some() {
4129 return Err(GitError::InvalidFormat(
4130 "upload-pack has duplicate object-format capability".into(),
4131 ));
4132 }
4133 validate_capability_field("upload-pack object-format", format)?;
4134 features.object_format = Some(format.parse()?);
4135 }
4136 "symref" => {
4137 let Some(symref) = &capability.value else {
4138 return Err(GitError::InvalidFormat(
4139 "upload-pack symref capability is missing value".into(),
4140 ));
4141 };
4142 validate_capability_field("upload-pack symref", symref)?;
4143 features.symrefs.push(symref.clone());
4144 }
4145 _ => {
4146 encode_capabilities(std::slice::from_ref(capability))?;
4147 if features
4148 .unknown
4149 .iter()
4150 .any(|known| known.name == capability.name)
4151 {
4152 return Err(GitError::InvalidFormat(format!(
4153 "upload-pack has duplicate {} capability",
4154 capability.name
4155 )));
4156 }
4157 features.unknown.push(capability.clone());
4158 }
4159 }
4160 }
4161 Ok(features)
4162}
4163
4164pub fn encode_upload_pack_features(features: &UploadPackFeatures) -> Result<Vec<Capability>> {
4165 let mut capabilities = Vec::new();
4166 push_upload_pack_flag(&mut capabilities, "multi_ack", features.multi_ack);
4167 push_upload_pack_flag(
4168 &mut capabilities,
4169 "multi_ack_detailed",
4170 features.multi_ack_detailed,
4171 );
4172 push_upload_pack_flag(&mut capabilities, "no-done", features.no_done);
4173 push_upload_pack_flag(&mut capabilities, "thin-pack", features.thin_pack);
4174 push_upload_pack_flag(&mut capabilities, "side-band", features.side_band);
4175 push_upload_pack_flag(&mut capabilities, "side-band-64k", features.side_band_64k);
4176 push_upload_pack_flag(&mut capabilities, "ofs-delta", features.ofs_delta);
4177 push_upload_pack_flag(&mut capabilities, "shallow", features.shallow);
4178 push_upload_pack_flag(&mut capabilities, "deepen-since", features.deepen_since);
4179 push_upload_pack_flag(&mut capabilities, "deepen-not", features.deepen_not);
4180 push_upload_pack_flag(&mut capabilities, "include-tag", features.include_tag);
4181 push_upload_pack_flag(&mut capabilities, "no-progress", features.no_progress);
4182 push_upload_pack_flag(
4183 &mut capabilities,
4184 "allow-tip-sha1-in-want",
4185 features.allow_tip_sha1_in_want,
4186 );
4187 push_upload_pack_flag(
4188 &mut capabilities,
4189 "allow-reachable-sha1-in-want",
4190 features.allow_reachable_sha1_in_want,
4191 );
4192 push_upload_pack_flag(&mut capabilities, "filter", features.filter);
4193 if let Some(agent) = &features.agent {
4194 validate_capability_field("upload-pack agent", agent)?;
4195 capabilities.push(Capability {
4196 name: "agent".into(),
4197 value: Some(agent.clone()),
4198 });
4199 }
4200 if let Some(format) = features.object_format {
4201 capabilities.push(Capability {
4202 name: "object-format".into(),
4203 value: Some(format.name().into()),
4204 });
4205 }
4206 for symref in &features.symrefs {
4207 validate_capability_field("upload-pack symref", symref)?;
4208 capabilities.push(Capability {
4209 name: "symref".into(),
4210 value: Some(symref.clone()),
4211 });
4212 }
4213 for capability in &features.unknown {
4214 if is_known_upload_pack_capability(&capability.name) {
4215 return Err(GitError::InvalidFormat(format!(
4216 "upload-pack unknown capability duplicates known capability {}",
4217 capability.name
4218 )));
4219 }
4220 encode_capabilities(std::slice::from_ref(capability))?;
4221 capabilities.push(capability.clone());
4222 }
4223 Ok(capabilities)
4224}
4225
4226pub fn validate_upload_pack_request_features(
4227 features: &UploadPackFeatures,
4228 request: &UploadPackRequest,
4229) -> Result<()> {
4230 for capability in &request.capabilities {
4231 if is_upload_pack_flag_capability(&capability.name) {
4232 reject_capability_value("upload-pack request capability", capability)?;
4233 }
4234 match capability.name.as_str() {
4235 "multi_ack" if !features.multi_ack => {
4236 return Err(GitError::InvalidFormat(
4237 "upload-pack request uses multi_ack without advertised capability".into(),
4238 ));
4239 }
4240 "multi_ack_detailed" if !features.multi_ack_detailed => {
4241 return Err(GitError::InvalidFormat(
4242 "upload-pack request uses multi_ack_detailed without advertised capability"
4243 .into(),
4244 ));
4245 }
4246 "no-done" if !features.no_done => {
4247 return Err(GitError::InvalidFormat(
4248 "upload-pack request uses no-done without advertised capability".into(),
4249 ));
4250 }
4251 "thin-pack" if !features.thin_pack => {
4252 return Err(GitError::InvalidFormat(
4253 "upload-pack request uses thin-pack without advertised capability".into(),
4254 ));
4255 }
4256 "side-band" if !features.side_band => {
4257 return Err(GitError::InvalidFormat(
4258 "upload-pack request uses side-band without advertised capability".into(),
4259 ));
4260 }
4261 "side-band-64k" if !features.side_band_64k => {
4262 return Err(GitError::InvalidFormat(
4263 "upload-pack request uses side-band-64k without advertised capability".into(),
4264 ));
4265 }
4266 "ofs-delta" if !features.ofs_delta => {
4267 return Err(GitError::InvalidFormat(
4268 "upload-pack request uses ofs-delta without advertised capability".into(),
4269 ));
4270 }
4271 "include-tag" if !features.include_tag => {
4272 return Err(GitError::InvalidFormat(
4273 "upload-pack request uses include-tag without advertised capability".into(),
4274 ));
4275 }
4276 "no-progress" if !features.no_progress => {
4277 return Err(GitError::InvalidFormat(
4278 "upload-pack request uses no-progress without advertised capability".into(),
4279 ));
4280 }
4281 "allow-tip-sha1-in-want" if !features.allow_tip_sha1_in_want => {
4282 return Err(GitError::InvalidFormat(
4283 "upload-pack request uses allow-tip-sha1-in-want without advertised capability"
4284 .into(),
4285 ));
4286 }
4287 "allow-reachable-sha1-in-want" if !features.allow_reachable_sha1_in_want => {
4288 return Err(GitError::InvalidFormat(
4289 "upload-pack request uses allow-reachable-sha1-in-want without advertised capability"
4290 .into(),
4291 ));
4292 }
4293 "filter" if !features.filter => {
4294 return Err(GitError::InvalidFormat(
4295 "upload-pack request uses filter capability without advertised capability"
4296 .into(),
4297 ));
4298 }
4299 "agent" => {
4300 let Some(agent) = &capability.value else {
4301 return Err(GitError::InvalidFormat(
4302 "upload-pack request agent capability is missing value".into(),
4303 ));
4304 };
4305 validate_capability_field("upload-pack request agent", agent)?;
4306 }
4307 "object-format" => {
4308 let Some(format) = &capability.value else {
4309 return Err(GitError::InvalidFormat(
4310 "upload-pack request object-format capability is missing value".into(),
4311 ));
4312 };
4313 let requested_format: ObjectFormat = format.parse()?;
4314 if features.object_format != Some(requested_format) {
4315 return Err(GitError::InvalidFormat(
4316 "upload-pack request object-format was not advertised".into(),
4317 ));
4318 }
4319 }
4320 name if is_known_upload_pack_capability(name) => {}
4321 _ => {
4322 if !features
4323 .unknown
4324 .iter()
4325 .any(|advertised| advertised.name == capability.name)
4326 {
4327 return Err(GitError::InvalidFormat(format!(
4328 "upload-pack request uses unadvertised capability {}",
4329 capability.name
4330 )));
4331 }
4332 }
4333 }
4334 }
4335
4336 let sideband = request
4337 .capabilities
4338 .iter()
4339 .any(|capability| capability.name == "side-band");
4340 let sideband_64k = request
4341 .capabilities
4342 .iter()
4343 .any(|capability| capability.name == "side-band-64k");
4344 if sideband && sideband_64k {
4345 return Err(GitError::InvalidFormat(
4346 "upload-pack request must not request both side-band and side-band-64k".into(),
4347 ));
4348 }
4349
4350 if !features.shallow && (!request.shallow.is_empty() || request.deepen.is_some()) {
4351 return Err(GitError::InvalidFormat(
4352 "upload-pack request uses shallow/deepen without advertised shallow capability".into(),
4353 ));
4354 }
4355 if !features.deepen_since && request.deepen_since.is_some() {
4356 return Err(GitError::InvalidFormat(
4357 "upload-pack request uses deepen-since without advertised capability".into(),
4358 ));
4359 }
4360 if !features.deepen_not && !request.deepen_not.is_empty() {
4361 return Err(GitError::InvalidFormat(
4362 "upload-pack request uses deepen-not without advertised capability".into(),
4363 ));
4364 }
4365 if !features.filter && request.filter.is_some() {
4366 return Err(GitError::InvalidFormat(
4367 "upload-pack request uses filter without advertised capability".into(),
4368 ));
4369 }
4370 Ok(())
4371}
4372
4373pub fn build_upload_pack_raw_packfile_response<C, B>(
4374 features: &UploadPackFeatures,
4375 request: UploadPackRequest,
4376 haves: impl IntoIterator<Item = ObjectId>,
4377 mut contains_object: C,
4378 mut build_pack: B,
4379) -> Result<UploadPackRawPackfileResponse>
4380where
4381 C: FnMut(&ObjectId) -> Result<bool>,
4382 B: FnMut(Vec<ObjectId>, Vec<ObjectId>) -> Result<Option<Vec<u8>>>,
4383{
4384 validate_upload_pack_request_features(features, &request)?;
4385 for want in &request.wants {
4386 if !contains_object(want)? {
4387 return Err(GitError::InvalidObject(format!(
4388 "upload-pack requested missing object {want}"
4389 )));
4390 }
4391 }
4392 let known_haves = haves
4393 .into_iter()
4394 .filter_map(|oid| match contains_object(&oid) {
4395 Ok(true) => Some(Ok(oid)),
4396 Ok(false) => None,
4397 Err(err) => Some(Err(err)),
4398 })
4399 .collect::<Result<Vec<_>>>()?;
4400 let packfile = build_pack(request.wants, known_haves)?
4401 .ok_or_else(|| GitError::InvalidObject("upload-pack request produced empty pack".into()))?;
4402 Ok(UploadPackRawPackfileResponse {
4403 acknowledgments: vec![UploadPackAcknowledgment::Nak],
4404 packfile,
4405 })
4406}
4407
4408pub fn parse_upload_pack_shallow_update(
4409 format: ObjectFormat,
4410 frames: &[PktLineFrame],
4411) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4412 let mut entries = Vec::new();
4413 let mut saw_flush = false;
4414 for (idx, frame) in frames.iter().enumerate() {
4415 match frame {
4416 PktLineFrame::Data(payload) if !saw_flush => {
4417 entries.push(parse_fetch_shallow_info(format, payload)?);
4418 }
4419 PktLineFrame::Data(_) => {
4420 return Err(GitError::InvalidFormat(
4421 "upload-pack shallow update has data after flush".into(),
4422 ));
4423 }
4424 PktLineFrame::Flush => {
4425 saw_flush = true;
4426 if idx + 1 != frames.len() {
4427 return Err(GitError::InvalidFormat(
4428 "upload-pack shallow update has frames after flush".into(),
4429 ));
4430 }
4431 }
4432 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4433 return Err(GitError::InvalidFormat(
4434 "upload-pack shallow update contains a non-flush control packet".into(),
4435 ));
4436 }
4437 }
4438 }
4439 if !saw_flush {
4440 return Err(GitError::InvalidFormat(
4441 "upload-pack shallow update missing flush".into(),
4442 ));
4443 }
4444 Ok(entries)
4445}
4446
4447pub fn encode_upload_pack_shallow_update(
4448 entries: &[ProtocolV2FetchShallowInfo],
4449) -> Result<Vec<PktLineFrame>> {
4450 let mut frames = Vec::new();
4451 for entry in entries {
4452 let line = match entry {
4453 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4454 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4455 };
4456 frames.push(PktLineFrame::data(line_from_str(&line))?);
4457 }
4458 frames.push(PktLineFrame::Flush);
4459 Ok(frames)
4460}
4461
4462pub fn read_upload_pack_shallow_update(
4463 format: ObjectFormat,
4464 reader: &mut impl Read,
4465) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
4466 let frames = read_pkt_line_frames_until_flush(reader)?;
4467 parse_upload_pack_shallow_update(format, &frames)
4468}
4469
4470pub fn write_upload_pack_shallow_update(
4471 writer: &mut impl Write,
4472 entries: &[ProtocolV2FetchShallowInfo],
4473) -> Result<()> {
4474 for entry in entries {
4475 let line = match entry {
4476 ProtocolV2FetchShallowInfo::Shallow(oid) => format!("shallow {oid}"),
4477 ProtocolV2FetchShallowInfo::Unshallow(oid) => format!("unshallow {oid}"),
4478 };
4479 write_pkt_line_payload(writer, &line_from_str(&line))?;
4480 }
4481 writer.write_all(b"0000")?;
4482 Ok(())
4483}
4484
4485pub fn parse_upload_pack_negotiation_request(
4486 format: ObjectFormat,
4487 frames: &[PktLineFrame],
4488) -> Result<UploadPackNegotiationRequest> {
4489 let mut request = UploadPackNegotiationRequest::default();
4490 let mut terminated = false;
4491 for (idx, frame) in frames.iter().enumerate() {
4492 match frame {
4493 PktLineFrame::Data(payload) if !terminated => {
4494 let text = parse_protocol_v2_line_text("upload-pack negotiation line", payload)?;
4495 if text == "done" {
4496 request.done = true;
4497 terminated = true;
4498 if idx + 1 != frames.len() {
4499 return Err(GitError::InvalidFormat(
4500 "upload-pack negotiation has frames after done".into(),
4501 ));
4502 }
4503 } else if text.starts_with("have ") {
4504 request.haves.push(parse_oid_argument(
4505 format,
4506 "upload-pack have",
4507 text,
4508 "have ",
4509 )?);
4510 } else {
4511 return Err(GitError::InvalidFormat(format!(
4512 "unsupported upload-pack negotiation line {text}"
4513 )));
4514 }
4515 }
4516 PktLineFrame::Data(_) => {
4517 return Err(GitError::InvalidFormat(
4518 "upload-pack negotiation has data after terminator".into(),
4519 ));
4520 }
4521 PktLineFrame::Flush => {
4522 terminated = true;
4523 if idx + 1 != frames.len() {
4524 return Err(GitError::InvalidFormat(
4525 "upload-pack negotiation has frames after flush".into(),
4526 ));
4527 }
4528 }
4529 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4530 return Err(GitError::InvalidFormat(
4531 "upload-pack negotiation contains a non-flush control packet".into(),
4532 ));
4533 }
4534 }
4535 }
4536 if !terminated {
4537 return Err(GitError::InvalidFormat(
4538 "upload-pack negotiation missing terminator".into(),
4539 ));
4540 }
4541 Ok(request)
4542}
4543
4544pub fn encode_upload_pack_negotiation_request(
4545 request: &UploadPackNegotiationRequest,
4546) -> Result<Vec<PktLineFrame>> {
4547 let mut frames = Vec::new();
4548 for oid in &request.haves {
4549 frames.push(PktLineFrame::data(line_from_str(&format!("have {oid}")))?);
4550 }
4551 if request.done {
4552 frames.push(PktLineFrame::data(line_from_str("done"))?);
4553 } else {
4554 frames.push(PktLineFrame::Flush);
4555 }
4556 Ok(frames)
4557}
4558
4559pub fn read_upload_pack_negotiation_request(
4560 format: ObjectFormat,
4561 reader: &mut impl Read,
4562) -> Result<UploadPackNegotiationRequest> {
4563 let mut frames = Vec::new();
4564 loop {
4565 let Some(frame) = read_pkt_line_frame(reader)? else {
4566 return Err(GitError::InvalidFormat(
4567 "pkt-line stream ended before upload-pack negotiation terminator".into(),
4568 ));
4569 };
4570 let done = match &frame {
4571 PktLineFrame::Flush => true,
4572 PktLineFrame::Data(payload) => trim_trailing_lf(payload) == b"done",
4573 _ => false,
4574 };
4575 frames.push(frame);
4576 if done {
4577 return parse_upload_pack_negotiation_request(format, &frames);
4578 }
4579 }
4580}
4581
4582pub fn write_upload_pack_negotiation_request(
4583 writer: &mut impl Write,
4584 request: &UploadPackNegotiationRequest,
4585) -> Result<()> {
4586 for oid in &request.haves {
4587 write_pkt_line_payload(writer, &line_from_str(&format!("have {oid}")))?;
4588 }
4589 if request.done {
4590 write_pkt_line_payload(writer, b"done\n")?;
4591 } else {
4592 writer.write_all(b"0000")?;
4593 }
4594 Ok(())
4595}
4596
4597pub fn parse_upload_pack_acknowledgment(
4598 format: ObjectFormat,
4599 payload: &[u8],
4600) -> Result<UploadPackAcknowledgment> {
4601 let text = parse_protocol_v2_line_text("upload-pack acknowledgment", payload)?;
4602 if text == "NAK" {
4603 return Ok(UploadPackAcknowledgment::Nak);
4604 }
4605 let Some(rest) = text.strip_prefix("ACK ") else {
4606 return Err(GitError::InvalidFormat(format!(
4607 "unsupported upload-pack acknowledgment {text}"
4608 )));
4609 };
4610 let mut fields = rest.split(' ');
4611 let oid = fields
4612 .next()
4613 .ok_or_else(|| GitError::InvalidFormat("upload-pack ACK missing object id".into()))?;
4614 validate_protocol_v2_token("upload-pack ACK", oid)?;
4615 let status = match fields.next() {
4616 None => None,
4617 Some("continue") => Some(UploadPackAckStatus::Continue),
4618 Some("common") => Some(UploadPackAckStatus::Common),
4619 Some("ready") => Some(UploadPackAckStatus::Ready),
4620 Some(other) => {
4621 return Err(GitError::InvalidFormat(format!(
4622 "unsupported upload-pack ACK status {other}"
4623 )));
4624 }
4625 };
4626 if fields.next().is_some() {
4627 return Err(GitError::InvalidFormat(
4628 "upload-pack ACK has too many fields".into(),
4629 ));
4630 }
4631 Ok(UploadPackAcknowledgment::Ack {
4632 oid: ObjectId::from_hex(format, oid)?,
4633 status,
4634 })
4635}
4636
4637pub fn encode_upload_pack_acknowledgment(
4638 acknowledgment: &UploadPackAcknowledgment,
4639) -> Result<Vec<u8>> {
4640 let line = match acknowledgment {
4641 UploadPackAcknowledgment::Nak => "NAK".to_string(),
4642 UploadPackAcknowledgment::Ack { oid, status } => {
4643 let mut line = format!("ACK {oid}");
4644 if let Some(status) = status {
4645 line.push(' ');
4646 line.push_str(match status {
4647 UploadPackAckStatus::Continue => "continue",
4648 UploadPackAckStatus::Common => "common",
4649 UploadPackAckStatus::Ready => "ready",
4650 });
4651 }
4652 line
4653 }
4654 };
4655 Ok(line_from_str(&line))
4656}
4657
4658pub fn read_upload_pack_acknowledgment(
4659 format: ObjectFormat,
4660 reader: &mut impl Read,
4661) -> Result<UploadPackAcknowledgment> {
4662 let Some(frame) = read_pkt_line_frame(reader)? else {
4663 return Err(GitError::InvalidFormat(
4664 "pkt-line stream ended before upload-pack acknowledgment".into(),
4665 ));
4666 };
4667 match frame {
4668 PktLineFrame::Data(payload) => parse_upload_pack_acknowledgment(format, &payload),
4669 _ => Err(GitError::InvalidFormat(
4670 "upload-pack acknowledgment must be a data packet".into(),
4671 )),
4672 }
4673}
4674
4675pub fn write_upload_pack_acknowledgment(
4676 writer: &mut impl Write,
4677 acknowledgment: &UploadPackAcknowledgment,
4678) -> Result<()> {
4679 write_pkt_line_payload(writer, &encode_upload_pack_acknowledgment(acknowledgment)?)
4680}
4681
4682pub fn parse_upload_pack_packfile_response(
4683 format: ObjectFormat,
4684 frames: &[PktLineFrame],
4685) -> Result<UploadPackPackfileResponse> {
4686 let mut response = UploadPackPackfileResponse::default();
4687 let mut in_sideband = false;
4688 let mut saw_flush = false;
4689 for (idx, frame) in frames.iter().enumerate() {
4690 match frame {
4691 PktLineFrame::Data(payload) if !saw_flush => {
4692 if !in_sideband
4693 && (trim_trailing_lf(payload) == b"NAK" || payload.starts_with(b"ACK "))
4694 {
4695 response
4696 .acknowledgments
4697 .push(parse_upload_pack_acknowledgment(format, payload)?);
4698 continue;
4699 }
4700 in_sideband = true;
4701 response.sideband.push(parse_sideband_packet(payload)?);
4702 }
4703 PktLineFrame::Data(_) => {
4704 return Err(GitError::InvalidFormat(
4705 "upload-pack packfile response has data after flush".into(),
4706 ));
4707 }
4708 PktLineFrame::Flush => {
4709 saw_flush = true;
4710 if idx + 1 != frames.len() {
4711 return Err(GitError::InvalidFormat(
4712 "upload-pack packfile response has frames after flush".into(),
4713 ));
4714 }
4715 }
4716 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4717 return Err(GitError::InvalidFormat(
4718 "upload-pack packfile response contains a non-flush control packet".into(),
4719 ));
4720 }
4721 }
4722 }
4723 if !saw_flush {
4724 return Err(GitError::InvalidFormat(
4725 "upload-pack packfile response missing flush".into(),
4726 ));
4727 }
4728 Ok(response)
4729}
4730
4731pub fn encode_upload_pack_packfile_response(
4732 response: &UploadPackPackfileResponse,
4733) -> Result<Vec<PktLineFrame>> {
4734 let mut frames = Vec::new();
4735 for acknowledgment in &response.acknowledgments {
4736 frames.push(PktLineFrame::data(encode_upload_pack_acknowledgment(
4737 acknowledgment,
4738 )?)?);
4739 }
4740 for packet in &response.sideband {
4741 frames.push(PktLineFrame::data(encode_sideband_packet(packet)?)?);
4742 }
4743 frames.push(PktLineFrame::Flush);
4744 Ok(frames)
4745}
4746
4747pub fn read_upload_pack_packfile_response(
4748 format: ObjectFormat,
4749 reader: &mut impl Read,
4750) -> Result<UploadPackPackfileResponse> {
4751 let frames = read_pkt_line_frames_until_flush(reader)?;
4752 parse_upload_pack_packfile_response(format, &frames)
4753}
4754
4755pub fn write_upload_pack_packfile_response(
4756 writer: &mut impl Write,
4757 response: &UploadPackPackfileResponse,
4758) -> Result<()> {
4759 for acknowledgment in &response.acknowledgments {
4760 write_upload_pack_acknowledgment(writer, acknowledgment)?;
4761 }
4762 for packet in &response.sideband {
4763 write_sideband_packet(writer, packet)?;
4764 }
4765 writer.write_all(b"0000")?;
4766 Ok(())
4767}
4768
4769pub fn demux_upload_pack_packfile_response(
4770 response: &UploadPackPackfileResponse,
4771) -> Result<SideBandDemux> {
4772 demux_sideband_packets(&response.sideband)
4773}
4774
4775pub fn parse_upload_pack_raw_packfile_response(
4776 format: ObjectFormat,
4777 input: &[u8],
4778) -> Result<UploadPackRawPackfileResponse> {
4779 let mut response = UploadPackRawPackfileResponse::default();
4780 let mut offset = 0usize;
4781 while offset < input.len() {
4782 match PktLineFrame::parse(&input[offset..]) {
4783 Ok((PktLineFrame::Data(payload), consumed)) => {
4784 let trimmed = trim_trailing_lf(&payload);
4785 if trimmed == b"NAK" || trimmed.starts_with(b"ACK ") {
4786 response
4787 .acknowledgments
4788 .push(parse_upload_pack_acknowledgment(format, &payload)?);
4789 offset += consumed;
4790 continue;
4791 }
4792 return Err(GitError::InvalidFormat(
4793 "upload-pack raw packfile response has non-ack pkt-line before packfile".into(),
4794 ));
4795 }
4796 Ok((PktLineFrame::Flush | PktLineFrame::Delimiter | PktLineFrame::ResponseEnd, _)) => {
4797 return Err(GitError::InvalidFormat(
4798 "upload-pack raw packfile response contains a control packet".into(),
4799 ));
4800 }
4801 Err(_) if input[offset..].starts_with(b"PACK") => break,
4802 Err(err) => return Err(err),
4803 }
4804 }
4805 response.packfile = input[offset..].to_vec();
4806 if response.packfile.is_empty() {
4807 return Err(GitError::InvalidFormat(
4808 "upload-pack raw packfile response missing packfile".into(),
4809 ));
4810 }
4811 if !response.packfile.starts_with(b"PACK") {
4812 return Err(GitError::InvalidFormat(
4813 "upload-pack raw packfile response packfile must start with PACK".into(),
4814 ));
4815 }
4816 Ok(response)
4817}
4818
4819pub fn encode_upload_pack_raw_packfile_response(
4820 response: &UploadPackRawPackfileResponse,
4821) -> Result<Vec<u8>> {
4822 if response.packfile.is_empty() {
4823 return Err(GitError::InvalidFormat(
4824 "upload-pack raw packfile response missing packfile".into(),
4825 ));
4826 }
4827 if !response.packfile.starts_with(b"PACK") {
4828 return Err(GitError::InvalidFormat(
4829 "upload-pack raw packfile response packfile must start with PACK".into(),
4830 ));
4831 }
4832 let mut out = Vec::new();
4833 for acknowledgment in &response.acknowledgments {
4834 write_pkt_line_payload(
4835 &mut out,
4836 &encode_upload_pack_acknowledgment(acknowledgment)?,
4837 )?;
4838 }
4839 out.extend_from_slice(&response.packfile);
4840 Ok(out)
4841}
4842
4843pub fn read_upload_pack_raw_packfile_response(
4844 format: ObjectFormat,
4845 reader: &mut impl Read,
4846) -> Result<UploadPackRawPackfileResponse> {
4847 let mut input = Vec::new();
4848 reader.read_to_end(&mut input)?;
4849 parse_upload_pack_raw_packfile_response(format, &input)
4850}
4851
4852pub fn write_upload_pack_raw_packfile_response(
4853 writer: &mut impl Write,
4854 response: &UploadPackRawPackfileResponse,
4855) -> Result<()> {
4856 if response.packfile.is_empty() {
4857 return Err(GitError::InvalidFormat(
4858 "upload-pack raw packfile response missing packfile".into(),
4859 ));
4860 }
4861 if !response.packfile.starts_with(b"PACK") {
4862 return Err(GitError::InvalidFormat(
4863 "upload-pack raw packfile response packfile must start with PACK".into(),
4864 ));
4865 }
4866 for acknowledgment in &response.acknowledgments {
4867 write_upload_pack_acknowledgment(writer, acknowledgment)?;
4868 }
4869 writer.write_all(&response.packfile)?;
4870 Ok(())
4871}
4872
4873pub fn parse_upload_pack_shallow_info_section(
4884 format: ObjectFormat,
4885 input: &[u8],
4886) -> Result<(Vec<ProtocolV2FetchShallowInfo>, usize)> {
4887 let mut entries = Vec::new();
4888 let mut offset = 0usize;
4889 loop {
4890 let (frame, consumed) = PktLineFrame::parse(&input[offset..])?;
4891 offset += consumed;
4892 match frame {
4893 PktLineFrame::Data(payload) => {
4894 entries.push(parse_fetch_shallow_info(format, &payload)?)
4895 }
4896 PktLineFrame::Flush => return Ok((entries, offset)),
4897 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
4898 return Err(GitError::InvalidFormat(
4899 "upload-pack shallow-info section contains a non-flush control packet".into(),
4900 ));
4901 }
4902 }
4903 }
4904}
4905
4906pub fn parse_upload_pack_shallow_info_and_raw_packfile_response(
4917 format: ObjectFormat,
4918 input: &[u8],
4919) -> Result<(
4920 Vec<ProtocolV2FetchShallowInfo>,
4921 UploadPackRawPackfileResponse,
4922)> {
4923 let (shallow, consumed) = parse_upload_pack_shallow_info_section(format, input)?;
4924 let response = parse_upload_pack_raw_packfile_response(format, &input[consumed..])?;
4925 Ok((shallow, response))
4926}
4927
4928pub fn read_upload_pack_shallow_info_and_raw_packfile_response(
4936 format: ObjectFormat,
4937 reader: &mut impl Read,
4938) -> Result<(
4939 Vec<ProtocolV2FetchShallowInfo>,
4940 UploadPackRawPackfileResponse,
4941)> {
4942 let mut input = Vec::new();
4943 reader.read_to_end(&mut input)?;
4944 parse_upload_pack_shallow_info_and_raw_packfile_response(format, &input)
4945}
4946
4947pub fn parse_receive_pack_request(
4948 format: ObjectFormat,
4949 frames: &[PktLineFrame],
4950) -> Result<ReceivePackRequest> {
4951 let mut request = ReceivePackRequest::default();
4952 let mut saw_command = false;
4953 let mut saw_flush = false;
4954 for (idx, frame) in frames.iter().enumerate() {
4955 match frame {
4956 PktLineFrame::Data(payload) if !saw_flush => {
4957 let payload = trim_trailing_lf(payload);
4958 if payload.is_empty() {
4959 return Err(GitError::InvalidFormat(
4960 "receive-pack request line is empty".into(),
4961 ));
4962 }
4963 if let Some(shallow) = payload.strip_prefix(b"shallow ") {
4964 if saw_command {
4965 return Err(GitError::InvalidFormat(
4966 "receive-pack request has shallow after commands".into(),
4967 ));
4968 }
4969 let shallow = std::str::from_utf8(shallow)
4970 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
4971 validate_protocol_v2_token("receive-pack shallow", shallow)?;
4972 request.shallow.push(ObjectId::from_hex(format, shallow)?);
4973 continue;
4974 }
4975
4976 let (command, capabilities) = match payload.iter().position(|byte| *byte == 0) {
4977 Some(nul) if !saw_command => (
4978 &payload[..nul],
4979 Some(parse_capabilities(&payload[nul + 1..])?),
4980 ),
4981 Some(_) => {
4982 return Err(GitError::InvalidFormat(
4983 "receive-pack capabilities must appear on the first command".into(),
4984 ));
4985 }
4986 None => (payload, None),
4987 };
4988 let command = parse_receive_pack_command(format, command)?;
4989 if let Some(capabilities) = capabilities {
4990 request.capabilities = capabilities;
4991 }
4992 request.commands.push(command);
4993 saw_command = true;
4994 }
4995 PktLineFrame::Data(_) => {
4996 return Err(GitError::InvalidFormat(
4997 "receive-pack request has data after flush".into(),
4998 ));
4999 }
5000 PktLineFrame::Flush => {
5001 saw_flush = true;
5002 if idx + 1 != frames.len() {
5003 return Err(GitError::InvalidFormat(
5004 "receive-pack request has frames after flush".into(),
5005 ));
5006 }
5007 }
5008 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5009 return Err(GitError::InvalidFormat(
5010 "receive-pack request contains a non-flush control packet".into(),
5011 ));
5012 }
5013 }
5014 }
5015 if !saw_flush {
5016 return Err(GitError::InvalidFormat(
5017 "receive-pack request missing flush".into(),
5018 ));
5019 }
5020 if !request.shallow.is_empty() && request.commands.is_empty() {
5021 return Err(GitError::InvalidFormat(
5022 "receive-pack request has shallow lines without commands".into(),
5023 ));
5024 }
5025 Ok(request)
5026}
5027
5028pub fn encode_receive_pack_request(request: &ReceivePackRequest) -> Result<Vec<PktLineFrame>> {
5029 if !request.shallow.is_empty() && request.commands.is_empty() {
5030 return Err(GitError::InvalidFormat(
5031 "receive-pack request has shallow lines without commands".into(),
5032 ));
5033 }
5034
5035 let mut frames = Vec::new();
5036 for oid in &request.shallow {
5037 frames.push(PktLineFrame::data(line_from_str(&format!(
5038 "shallow {oid}"
5039 )))?);
5040 }
5041 for (idx, command) in request.commands.iter().enumerate() {
5042 let mut payload = format_receive_pack_command(command)?;
5043 if idx == 0 && !request.capabilities.is_empty() {
5044 payload.push(0);
5045 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5046 }
5047 payload.push(b'\n');
5048 frames.push(PktLineFrame::data(payload)?);
5049 }
5050 frames.push(PktLineFrame::Flush);
5051 Ok(frames)
5052}
5053
5054pub fn read_receive_pack_request(
5055 format: ObjectFormat,
5056 reader: &mut impl Read,
5057) -> Result<ReceivePackRequest> {
5058 let frames = read_pkt_line_frames_until_flush(reader)?;
5059 parse_receive_pack_request(format, &frames)
5060}
5061
5062pub fn write_receive_pack_request(
5063 writer: &mut impl Write,
5064 request: &ReceivePackRequest,
5065) -> Result<()> {
5066 if !request.shallow.is_empty() && request.commands.is_empty() {
5067 return Err(GitError::InvalidFormat(
5068 "receive-pack request has shallow lines without commands".into(),
5069 ));
5070 }
5071
5072 for oid in &request.shallow {
5073 write_pkt_line_payload(writer, &line_from_str(&format!("shallow {oid}")))?;
5074 }
5075 for (idx, command) in request.commands.iter().enumerate() {
5076 let mut payload = format_receive_pack_command(command)?;
5077 if idx == 0 && !request.capabilities.is_empty() {
5078 payload.push(0);
5079 payload.extend_from_slice(&encode_capabilities(&request.capabilities)?);
5080 }
5081 payload.push(b'\n');
5082 write_pkt_line_payload(writer, &payload)?;
5083 }
5084 writer.write_all(b"0000")?;
5085 Ok(())
5086}
5087
5088pub fn parse_receive_pack_push_request(
5089 format: ObjectFormat,
5090 input: &[u8],
5091 has_push_options: bool,
5092) -> Result<ReceivePackPushRequest> {
5093 let (command_frames, consumed) = parse_pkt_line_frames_until_flush_from(input)?;
5094 let commands = parse_receive_pack_request(format, &command_frames)?;
5095 let mut offset = consumed;
5096 let push_options = if has_push_options {
5097 let (push_option_frames, consumed) =
5098 parse_pkt_line_frames_until_flush_from(&input[offset..])?;
5099 offset += consumed;
5100 Some(parse_receive_pack_push_options(&push_option_frames)?)
5101 } else {
5102 None
5103 };
5104 Ok(ReceivePackPushRequest {
5105 commands,
5106 push_options,
5107 packfile: input[offset..].to_vec(),
5108 })
5109}
5110
5111pub fn encode_receive_pack_push_request(request: &ReceivePackPushRequest) -> Result<Vec<u8>> {
5112 let mut out = Vec::new();
5113 write_receive_pack_request(&mut out, &request.commands)?;
5114 if let Some(push_options) = &request.push_options {
5115 write_receive_pack_push_options(&mut out, push_options)?;
5116 }
5117 out.extend_from_slice(&request.packfile);
5118 Ok(out)
5119}
5120
5121pub fn read_receive_pack_push_request(
5122 format: ObjectFormat,
5123 reader: &mut impl Read,
5124 has_push_options: bool,
5125) -> Result<ReceivePackPushRequest> {
5126 let commands = read_receive_pack_request(format, reader)?;
5127 let push_options = if has_push_options {
5128 Some(read_receive_pack_push_options(reader)?)
5129 } else {
5130 None
5131 };
5132 let mut packfile = Vec::new();
5133 reader.read_to_end(&mut packfile)?;
5134 Ok(ReceivePackPushRequest {
5135 commands,
5136 push_options,
5137 packfile,
5138 })
5139}
5140
5141pub fn write_receive_pack_push_request(
5142 writer: &mut impl Write,
5143 request: &ReceivePackPushRequest,
5144) -> Result<()> {
5145 write_receive_pack_request(writer, &request.commands)?;
5146 if let Some(push_options) = &request.push_options {
5147 write_receive_pack_push_options(writer, push_options)?;
5148 }
5149 writer.write_all(&request.packfile)?;
5150 Ok(())
5151}
5152
5153pub fn parse_receive_pack_features(capabilities: &[Capability]) -> Result<ReceivePackFeatures> {
5154 let mut features = ReceivePackFeatures::default();
5155 for capability in capabilities {
5156 match capability.name.as_str() {
5157 "report-status" => {
5158 reject_capability_value("receive-pack report-status", capability)?;
5159 if features.report_status {
5160 return Err(GitError::InvalidFormat(
5161 "receive-pack has duplicate report-status capability".into(),
5162 ));
5163 }
5164 features.report_status = true;
5165 }
5166 "report-status-v2" => {
5167 reject_capability_value("receive-pack report-status-v2", capability)?;
5168 if features.report_status_v2 {
5169 return Err(GitError::InvalidFormat(
5170 "receive-pack has duplicate report-status-v2 capability".into(),
5171 ));
5172 }
5173 features.report_status_v2 = true;
5174 }
5175 "delete-refs" => {
5176 reject_capability_value("receive-pack delete-refs", capability)?;
5177 if features.delete_refs {
5178 return Err(GitError::InvalidFormat(
5179 "receive-pack has duplicate delete-refs capability".into(),
5180 ));
5181 }
5182 features.delete_refs = true;
5183 }
5184 "ofs-delta" => {
5185 reject_capability_value("receive-pack ofs-delta", capability)?;
5186 if features.ofs_delta {
5187 return Err(GitError::InvalidFormat(
5188 "receive-pack has duplicate ofs-delta capability".into(),
5189 ));
5190 }
5191 features.ofs_delta = true;
5192 }
5193 "atomic" => {
5194 reject_capability_value("receive-pack atomic", capability)?;
5195 if features.atomic {
5196 return Err(GitError::InvalidFormat(
5197 "receive-pack has duplicate atomic capability".into(),
5198 ));
5199 }
5200 features.atomic = true;
5201 }
5202 "push-options" => {
5203 reject_capability_value("receive-pack push-options", capability)?;
5204 if features.push_options {
5205 return Err(GitError::InvalidFormat(
5206 "receive-pack has duplicate push-options capability".into(),
5207 ));
5208 }
5209 features.push_options = true;
5210 }
5211 "side-band-64k" => {
5212 reject_capability_value("receive-pack side-band-64k", capability)?;
5213 if features.side_band_64k {
5214 return Err(GitError::InvalidFormat(
5215 "receive-pack has duplicate side-band-64k capability".into(),
5216 ));
5217 }
5218 features.side_band_64k = true;
5219 }
5220 "quiet" => {
5221 reject_capability_value("receive-pack quiet", capability)?;
5222 if features.quiet {
5223 return Err(GitError::InvalidFormat(
5224 "receive-pack has duplicate quiet capability".into(),
5225 ));
5226 }
5227 features.quiet = true;
5228 }
5229 "no-thin" => {
5230 reject_capability_value("receive-pack no-thin", capability)?;
5231 if features.no_thin {
5232 return Err(GitError::InvalidFormat(
5233 "receive-pack has duplicate no-thin capability".into(),
5234 ));
5235 }
5236 features.no_thin = true;
5237 }
5238 "agent" => {
5239 let Some(agent) = &capability.value else {
5240 return Err(GitError::InvalidFormat(
5241 "receive-pack agent capability is missing value".into(),
5242 ));
5243 };
5244 if features.agent.is_some() {
5245 return Err(GitError::InvalidFormat(
5246 "receive-pack has duplicate agent capability".into(),
5247 ));
5248 }
5249 validate_capability_field("receive-pack agent", agent)?;
5250 features.agent = Some(agent.clone());
5251 }
5252 "object-format" => {
5253 let Some(format) = &capability.value else {
5254 return Err(GitError::InvalidFormat(
5255 "receive-pack object-format capability is missing value".into(),
5256 ));
5257 };
5258 if features.object_format.is_some() {
5259 return Err(GitError::InvalidFormat(
5260 "receive-pack has duplicate object-format capability".into(),
5261 ));
5262 }
5263 validate_capability_field("receive-pack object-format", format)?;
5264 features.object_format = Some(format.parse()?);
5265 }
5266 _ => {
5267 encode_capabilities(std::slice::from_ref(capability))?;
5268 if features
5269 .unknown
5270 .iter()
5271 .any(|known| known.name == capability.name)
5272 {
5273 return Err(GitError::InvalidFormat(format!(
5274 "receive-pack has duplicate {} capability",
5275 capability.name
5276 )));
5277 }
5278 features.unknown.push(capability.clone());
5279 }
5280 }
5281 }
5282 Ok(features)
5283}
5284
5285pub fn encode_receive_pack_features(features: &ReceivePackFeatures) -> Result<Vec<Capability>> {
5286 let mut capabilities = Vec::new();
5287 if features.report_status {
5288 capabilities.push(Capability {
5289 name: "report-status".into(),
5290 value: None,
5291 });
5292 }
5293 if features.report_status_v2 {
5294 capabilities.push(Capability {
5295 name: "report-status-v2".into(),
5296 value: None,
5297 });
5298 }
5299 if features.delete_refs {
5300 capabilities.push(Capability {
5301 name: "delete-refs".into(),
5302 value: None,
5303 });
5304 }
5305 if features.ofs_delta {
5306 capabilities.push(Capability {
5307 name: "ofs-delta".into(),
5308 value: None,
5309 });
5310 }
5311 if features.atomic {
5312 capabilities.push(Capability {
5313 name: "atomic".into(),
5314 value: None,
5315 });
5316 }
5317 if features.push_options {
5318 capabilities.push(Capability {
5319 name: "push-options".into(),
5320 value: None,
5321 });
5322 }
5323 if features.side_band_64k {
5324 capabilities.push(Capability {
5325 name: "side-band-64k".into(),
5326 value: None,
5327 });
5328 }
5329 if features.quiet {
5330 capabilities.push(Capability {
5331 name: "quiet".into(),
5332 value: None,
5333 });
5334 }
5335 if features.no_thin {
5336 capabilities.push(Capability {
5337 name: "no-thin".into(),
5338 value: None,
5339 });
5340 }
5341 if let Some(agent) = &features.agent {
5342 validate_capability_field("receive-pack agent", agent)?;
5343 capabilities.push(Capability {
5344 name: "agent".into(),
5345 value: Some(agent.clone()),
5346 });
5347 }
5348 if let Some(format) = features.object_format {
5349 capabilities.push(Capability {
5350 name: "object-format".into(),
5351 value: Some(format.name().into()),
5352 });
5353 }
5354 for capability in &features.unknown {
5355 if is_known_receive_pack_capability(&capability.name) {
5356 return Err(GitError::InvalidFormat(format!(
5357 "receive-pack unknown capability duplicates known capability {}",
5358 capability.name
5359 )));
5360 }
5361 encode_capabilities(std::slice::from_ref(capability))?;
5362 capabilities.push(capability.clone());
5363 }
5364 Ok(capabilities)
5365}
5366
5367pub fn validate_receive_pack_push_request_features(
5368 features: &ReceivePackFeatures,
5369 request: &ReceivePackPushRequest,
5370) -> Result<()> {
5371 for capability in &request.commands.capabilities {
5372 if matches!(
5373 capability.name.as_str(),
5374 "report-status"
5375 | "report-status-v2"
5376 | "delete-refs"
5377 | "ofs-delta"
5378 | "atomic"
5379 | "push-options"
5380 | "side-band-64k"
5381 | "quiet"
5382 | "no-thin"
5383 ) {
5384 reject_capability_value("receive-pack request capability", capability)?;
5385 }
5386 match capability.name.as_str() {
5387 "report-status" if !features.report_status => {
5388 return Err(GitError::InvalidFormat(
5389 "receive-pack request uses report-status without advertised capability".into(),
5390 ));
5391 }
5392 "report-status-v2" if !features.report_status_v2 => {
5393 return Err(GitError::InvalidFormat(
5394 "receive-pack request uses report-status-v2 without advertised capability"
5395 .into(),
5396 ));
5397 }
5398 "delete-refs" if !features.delete_refs => {
5399 return Err(GitError::InvalidFormat(
5400 "receive-pack request uses delete-refs without advertised capability".into(),
5401 ));
5402 }
5403 "ofs-delta" if !features.ofs_delta => {
5404 return Err(GitError::InvalidFormat(
5405 "receive-pack request uses ofs-delta without advertised capability".into(),
5406 ));
5407 }
5408 "atomic" if !features.atomic => {
5409 return Err(GitError::InvalidFormat(
5410 "receive-pack request uses atomic without advertised capability".into(),
5411 ));
5412 }
5413 "push-options" if !features.push_options => {
5414 return Err(GitError::InvalidFormat(
5415 "receive-pack request uses push-options without advertised capability".into(),
5416 ));
5417 }
5418 "side-band-64k" if !features.side_band_64k => {
5419 return Err(GitError::InvalidFormat(
5420 "receive-pack request uses side-band-64k without advertised capability".into(),
5421 ));
5422 }
5423 "quiet" if !features.quiet => {
5424 return Err(GitError::InvalidFormat(
5425 "receive-pack request uses quiet without advertised capability".into(),
5426 ));
5427 }
5428 "no-thin" => {
5429 return Err(GitError::InvalidFormat(
5430 "receive-pack request must not request no-thin".into(),
5431 ));
5432 }
5433 "agent" => {
5434 validate_capability_field(
5435 "receive-pack request agent",
5436 capability.value.as_deref().unwrap_or_default(),
5437 )?;
5438 }
5439 "object-format" => {
5440 let Some(value) = &capability.value else {
5441 return Err(GitError::InvalidFormat(
5442 "receive-pack request object-format capability is missing value".into(),
5443 ));
5444 };
5445 let requested_format: ObjectFormat = value.parse()?;
5446 if features.object_format != Some(requested_format) {
5447 return Err(GitError::InvalidFormat(
5448 "receive-pack request object-format was not advertised".into(),
5449 ));
5450 }
5451 }
5452 name if is_known_receive_pack_capability(name) => {}
5453 _ => {
5454 if !features
5455 .unknown
5456 .iter()
5457 .any(|advertised| advertised.name == capability.name)
5458 {
5459 return Err(GitError::InvalidFormat(format!(
5460 "receive-pack request uses unadvertised capability {}",
5461 capability.name
5462 )));
5463 }
5464 }
5465 }
5466 }
5467
5468 let requested_push_options = request
5469 .commands
5470 .capabilities
5471 .iter()
5472 .any(|capability| capability.name == "push-options");
5473 match (requested_push_options, &request.push_options) {
5474 (true, Some(_)) => {}
5475 (true, None) => {
5476 return Err(GitError::InvalidFormat(
5477 "receive-pack request uses push-options without push-options section".into(),
5478 ));
5479 }
5480 (false, Some(_)) => {
5481 return Err(GitError::InvalidFormat(
5482 "receive-pack request has push-options section without requested capability".into(),
5483 ));
5484 }
5485 (false, None) => {}
5486 }
5487
5488 let has_delete = request
5489 .commands
5490 .commands
5491 .iter()
5492 .any(is_receive_pack_delete_command);
5493 if has_delete && !features.delete_refs {
5494 return Err(GitError::InvalidFormat(
5495 "receive-pack request deletes refs without advertised delete-refs capability".into(),
5496 ));
5497 }
5498
5499 let has_update_or_create = request
5500 .commands
5501 .commands
5502 .iter()
5503 .any(|command| !is_receive_pack_delete_command(command));
5504 if has_update_or_create && request.packfile.is_empty() {
5505 return Err(GitError::InvalidFormat(
5506 "receive-pack request with create/update commands is missing packfile".into(),
5507 ));
5508 }
5509 if !has_update_or_create && !request.packfile.is_empty() {
5510 return Err(GitError::InvalidFormat(
5511 "receive-pack delete-only request must not include packfile".into(),
5512 ));
5513 }
5514 Ok(())
5515}
5516
5517pub fn apply_receive_pack_push_request<R, I, C, U, D>(
5518 features: &ReceivePackFeatures,
5519 request: &ReceivePackPushRequest,
5520 mut read_ref: R,
5521 mut install_pack: I,
5522 mut contains_object: C,
5523 mut apply_updates: U,
5524 mut delete_ref: D,
5525) -> Result<ReceivePackReportStatus>
5526where
5527 R: FnMut(&str) -> Result<Option<ObjectId>>,
5528 I: FnMut(&[u8]) -> Result<()>,
5529 C: FnMut(&ObjectId) -> Result<bool>,
5530 U: FnMut(&[ReceivePackCommand]) -> Result<()>,
5531 D: FnMut(&ReceivePackCommand) -> Result<()>,
5532{
5533 validate_receive_pack_push_request_features(features, request)?;
5534
5535 for command in request
5536 .commands
5537 .commands
5538 .iter()
5539 .filter(|command| is_receive_pack_delete_command(command))
5540 {
5541 if !command.old_id.is_null() && read_ref(&command.name)? != Some(command.old_id.clone()) {
5542 return Err(GitError::Transaction(format!(
5543 "expected ref {} to match",
5544 command.name
5545 )));
5546 }
5547 }
5548
5549 let updates = request
5550 .commands
5551 .commands
5552 .iter()
5553 .filter(|command| !is_receive_pack_delete_command(command))
5554 .cloned()
5555 .collect::<Vec<_>>();
5556 if !updates.is_empty() {
5557 install_pack(&request.packfile)?;
5558 for command in &updates {
5559 if !contains_object(&command.new_id)? {
5560 return Err(GitError::InvalidObject(format!(
5561 "receive-pack packfile did not provide {}",
5562 command.new_id
5563 )));
5564 }
5565 }
5566 apply_updates(&updates)?;
5567 }
5568
5569 for command in request
5570 .commands
5571 .commands
5572 .iter()
5573 .filter(|command| is_receive_pack_delete_command(command))
5574 {
5575 delete_ref(command)?;
5576 }
5577
5578 Ok(ReceivePackReportStatus {
5579 unpack: ReceivePackUnpackStatus::Ok,
5580 commands: request
5581 .commands
5582 .commands
5583 .iter()
5584 .map(|command| ReceivePackCommandStatus::Ok {
5585 name: command.name.clone(),
5586 })
5587 .collect(),
5588 })
5589}
5590
5591pub fn parse_receive_pack_report_status(
5592 frames: &[PktLineFrame],
5593) -> Result<ReceivePackReportStatus> {
5594 let Some((first, rest)) = frames.split_first() else {
5595 return Err(GitError::InvalidFormat(
5596 "receive-pack report-status is empty".into(),
5597 ));
5598 };
5599 let PktLineFrame::Data(payload) = first else {
5600 return Err(GitError::InvalidFormat(
5601 "receive-pack report-status must start with unpack status".into(),
5602 ));
5603 };
5604 let unpack = parse_receive_pack_unpack_status(payload)?;
5605
5606 let mut commands = Vec::new();
5607 let mut saw_flush = false;
5608 for (idx, frame) in rest.iter().enumerate() {
5609 match frame {
5610 PktLineFrame::Data(payload) if !saw_flush => {
5611 commands.push(parse_receive_pack_command_status(payload)?);
5612 }
5613 PktLineFrame::Data(_) => {
5614 return Err(GitError::InvalidFormat(
5615 "receive-pack report-status has data after flush".into(),
5616 ));
5617 }
5618 PktLineFrame::Flush => {
5619 saw_flush = true;
5620 if idx + 1 != rest.len() {
5621 return Err(GitError::InvalidFormat(
5622 "receive-pack report-status has frames after flush".into(),
5623 ));
5624 }
5625 }
5626 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5627 return Err(GitError::InvalidFormat(
5628 "receive-pack report-status contains a non-flush control packet".into(),
5629 ));
5630 }
5631 }
5632 }
5633 if !saw_flush {
5634 return Err(GitError::InvalidFormat(
5635 "receive-pack report-status missing flush".into(),
5636 ));
5637 }
5638 Ok(ReceivePackReportStatus { unpack, commands })
5639}
5640
5641pub fn encode_receive_pack_report_status(
5642 report: &ReceivePackReportStatus,
5643) -> Result<Vec<PktLineFrame>> {
5644 let mut frames = Vec::new();
5645 frames.push(PktLineFrame::data(line_from_str(
5646 &format_receive_pack_unpack_status(&report.unpack)?,
5647 ))?);
5648 for command in &report.commands {
5649 frames.push(PktLineFrame::data(line_from_str(
5650 &format_receive_pack_command_status(command)?,
5651 ))?);
5652 }
5653 frames.push(PktLineFrame::Flush);
5654 Ok(frames)
5655}
5656
5657pub fn read_receive_pack_report_status(reader: &mut impl Read) -> Result<ReceivePackReportStatus> {
5658 let frames = read_pkt_line_frames_until_flush(reader)?;
5659 parse_receive_pack_report_status(&frames)
5660}
5661
5662pub fn write_receive_pack_report_status(
5663 writer: &mut impl Write,
5664 report: &ReceivePackReportStatus,
5665) -> Result<()> {
5666 write_pkt_line_payload(
5667 writer,
5668 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5669 )?;
5670 for command in &report.commands {
5671 write_pkt_line_payload(
5672 writer,
5673 &line_from_str(&format_receive_pack_command_status(command)?),
5674 )?;
5675 }
5676 writer.write_all(b"0000")?;
5677 Ok(())
5678}
5679
5680pub fn parse_receive_pack_report_status_v2(
5681 format: ObjectFormat,
5682 frames: &[PktLineFrame],
5683) -> Result<ReceivePackReportStatusV2> {
5684 let Some((first, rest)) = frames.split_first() else {
5685 return Err(GitError::InvalidFormat(
5686 "receive-pack report-status-v2 is empty".into(),
5687 ));
5688 };
5689 let PktLineFrame::Data(payload) = first else {
5690 return Err(GitError::InvalidFormat(
5691 "receive-pack report-status-v2 must start with unpack status".into(),
5692 ));
5693 };
5694 let unpack = parse_receive_pack_unpack_status(payload)?;
5695
5696 let mut commands = Vec::new();
5697 let mut saw_flush = false;
5698 for (idx, frame) in rest.iter().enumerate() {
5699 match frame {
5700 PktLineFrame::Data(payload) if !saw_flush => {
5701 let text =
5702 parse_protocol_v2_line_text("receive-pack report-status-v2 line", payload)?;
5703 if text.starts_with("option ") {
5704 let Some(ReceivePackCommandStatusV2::Ok { options, .. }) = commands.last_mut()
5705 else {
5706 return Err(GitError::InvalidFormat(
5707 "receive-pack report-status-v2 option without ok status".into(),
5708 ));
5709 };
5710 parse_receive_pack_report_status_v2_option(format, text, options)?;
5711 } else {
5712 commands.push(parse_receive_pack_command_status_v2(text)?);
5713 }
5714 }
5715 PktLineFrame::Data(_) => {
5716 return Err(GitError::InvalidFormat(
5717 "receive-pack report-status-v2 has data after flush".into(),
5718 ));
5719 }
5720 PktLineFrame::Flush => {
5721 saw_flush = true;
5722 if idx + 1 != rest.len() {
5723 return Err(GitError::InvalidFormat(
5724 "receive-pack report-status-v2 has frames after flush".into(),
5725 ));
5726 }
5727 }
5728 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5729 return Err(GitError::InvalidFormat(
5730 "receive-pack report-status-v2 contains a non-flush control packet".into(),
5731 ));
5732 }
5733 }
5734 }
5735 if !saw_flush {
5736 return Err(GitError::InvalidFormat(
5737 "receive-pack report-status-v2 missing flush".into(),
5738 ));
5739 }
5740 Ok(ReceivePackReportStatusV2 { unpack, commands })
5741}
5742
5743pub fn encode_receive_pack_report_status_v2(
5744 report: &ReceivePackReportStatusV2,
5745) -> Result<Vec<PktLineFrame>> {
5746 let mut frames = Vec::new();
5747 frames.push(PktLineFrame::data(line_from_str(
5748 &format_receive_pack_unpack_status(&report.unpack)?,
5749 ))?);
5750 for command in &report.commands {
5751 frames.push(PktLineFrame::data(line_from_str(
5752 &format_receive_pack_command_status_v2(command)?,
5753 ))?);
5754 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5755 for option in format_receive_pack_report_status_v2_options(options)? {
5756 frames.push(PktLineFrame::data(line_from_str(&option))?);
5757 }
5758 }
5759 }
5760 frames.push(PktLineFrame::Flush);
5761 Ok(frames)
5762}
5763
5764pub fn read_receive_pack_report_status_v2(
5765 format: ObjectFormat,
5766 reader: &mut impl Read,
5767) -> Result<ReceivePackReportStatusV2> {
5768 let frames = read_pkt_line_frames_until_flush(reader)?;
5769 parse_receive_pack_report_status_v2(format, &frames)
5770}
5771
5772pub fn write_receive_pack_report_status_v2(
5773 writer: &mut impl Write,
5774 report: &ReceivePackReportStatusV2,
5775) -> Result<()> {
5776 write_pkt_line_payload(
5777 writer,
5778 &line_from_str(&format_receive_pack_unpack_status(&report.unpack)?),
5779 )?;
5780 for command in &report.commands {
5781 write_pkt_line_payload(
5782 writer,
5783 &line_from_str(&format_receive_pack_command_status_v2(command)?),
5784 )?;
5785 if let ReceivePackCommandStatusV2::Ok { options, .. } = command {
5786 for option in format_receive_pack_report_status_v2_options(options)? {
5787 write_pkt_line_payload(writer, &line_from_str(&option))?;
5788 }
5789 }
5790 }
5791 writer.write_all(b"0000")?;
5792 Ok(())
5793}
5794
5795pub fn parse_receive_pack_push_options(frames: &[PktLineFrame]) -> Result<Vec<String>> {
5796 let mut options = Vec::new();
5797 let mut saw_flush = false;
5798 for (idx, frame) in frames.iter().enumerate() {
5799 match frame {
5800 PktLineFrame::Data(payload) if !saw_flush => {
5801 let option = trim_trailing_lf(payload);
5802 validate_receive_pack_push_option(option)?;
5803 options.push(
5804 std::str::from_utf8(option)
5805 .map_err(|err| GitError::InvalidFormat(err.to_string()))?
5806 .to_string(),
5807 );
5808 }
5809 PktLineFrame::Data(_) => {
5810 return Err(GitError::InvalidFormat(
5811 "receive-pack push-options has data after flush".into(),
5812 ));
5813 }
5814 PktLineFrame::Flush => {
5815 saw_flush = true;
5816 if idx + 1 != frames.len() {
5817 return Err(GitError::InvalidFormat(
5818 "receive-pack push-options has frames after flush".into(),
5819 ));
5820 }
5821 }
5822 PktLineFrame::Delimiter | PktLineFrame::ResponseEnd => {
5823 return Err(GitError::InvalidFormat(
5824 "receive-pack push-options contains a non-flush control packet".into(),
5825 ));
5826 }
5827 }
5828 }
5829 if !saw_flush {
5830 return Err(GitError::InvalidFormat(
5831 "receive-pack push-options missing flush".into(),
5832 ));
5833 }
5834 Ok(options)
5835}
5836
5837pub fn encode_receive_pack_push_options(options: &[String]) -> Result<Vec<PktLineFrame>> {
5838 let mut frames = Vec::new();
5839 for option in options {
5840 validate_receive_pack_push_option(option.as_bytes())?;
5841 let mut payload = option.as_bytes().to_vec();
5842 payload.push(b'\n');
5843 frames.push(PktLineFrame::data(payload)?);
5844 }
5845 frames.push(PktLineFrame::Flush);
5846 Ok(frames)
5847}
5848
5849pub fn read_receive_pack_push_options(reader: &mut impl Read) -> Result<Vec<String>> {
5850 let frames = read_pkt_line_frames_until_flush(reader)?;
5851 parse_receive_pack_push_options(&frames)
5852}
5853
5854pub fn write_receive_pack_push_options(writer: &mut impl Write, options: &[String]) -> Result<()> {
5855 for option in options {
5856 validate_receive_pack_push_option(option.as_bytes())?;
5857 let mut payload = option.as_bytes().to_vec();
5858 payload.push(b'\n');
5859 write_pkt_line_payload(writer, &payload)?;
5860 }
5861 writer.write_all(b"0000")?;
5862 Ok(())
5863}
5864
5865fn parse_receive_pack_command(format: ObjectFormat, payload: &[u8]) -> Result<ReceivePackCommand> {
5866 let text =
5867 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
5868 let mut fields = text.split(' ');
5869 let old_id = fields
5870 .next()
5871 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing old id".into()))?;
5872 let new_id = fields
5873 .next()
5874 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing new id".into()))?;
5875 let name = fields
5876 .next()
5877 .ok_or_else(|| GitError::InvalidFormat("receive-pack command missing ref name".into()))?;
5878 if fields.next().is_some() {
5879 return Err(GitError::InvalidFormat(
5880 "receive-pack command has too many fields".into(),
5881 ));
5882 }
5883 validate_protocol_v2_token("receive-pack old id", old_id)?;
5884 validate_protocol_v2_token("receive-pack new id", new_id)?;
5885 validate_protocol_v2_token("receive-pack ref name", name)?;
5886 Ok(ReceivePackCommand {
5887 old_id: ObjectId::from_hex(format, old_id)?,
5888 new_id: ObjectId::from_hex(format, new_id)?,
5889 name: name.to_string(),
5890 })
5891}
5892
5893fn format_receive_pack_command(command: &ReceivePackCommand) -> Result<Vec<u8>> {
5894 if command.old_id.format() != command.new_id.format() {
5895 return Err(GitError::InvalidObjectId(
5896 "receive-pack command object formats do not match".into(),
5897 ));
5898 }
5899 validate_protocol_v2_token("receive-pack ref name", &command.name)?;
5900 Ok(format!("{} {} {}", command.old_id, command.new_id, command.name).into_bytes())
5901}
5902
5903fn set_upload_pack_flag(value: &mut bool, capability: &Capability) -> Result<()> {
5904 reject_capability_value("upload-pack capability", capability)?;
5905 if *value {
5906 return Err(GitError::InvalidFormat(format!(
5907 "upload-pack has duplicate {} capability",
5908 capability.name
5909 )));
5910 }
5911 *value = true;
5912 Ok(())
5913}
5914
5915fn push_upload_pack_flag(capabilities: &mut Vec<Capability>, name: &str, enabled: bool) {
5916 if enabled {
5917 capabilities.push(Capability {
5918 name: name.into(),
5919 value: None,
5920 });
5921 }
5922}
5923
5924fn is_known_upload_pack_capability(name: &str) -> bool {
5925 matches!(
5926 name,
5927 "multi_ack"
5928 | "multi_ack_detailed"
5929 | "no-done"
5930 | "thin-pack"
5931 | "side-band"
5932 | "side-band-64k"
5933 | "ofs-delta"
5934 | "shallow"
5935 | "deepen-since"
5936 | "deepen-not"
5937 | "include-tag"
5938 | "no-progress"
5939 | "allow-tip-sha1-in-want"
5940 | "allow-reachable-sha1-in-want"
5941 | "filter"
5942 | "agent"
5943 | "object-format"
5944 | "symref"
5945 )
5946}
5947
5948fn is_upload_pack_flag_capability(name: &str) -> bool {
5949 matches!(
5950 name,
5951 "multi_ack"
5952 | "multi_ack_detailed"
5953 | "no-done"
5954 | "thin-pack"
5955 | "side-band"
5956 | "side-band-64k"
5957 | "ofs-delta"
5958 | "shallow"
5959 | "deepen-since"
5960 | "deepen-not"
5961 | "include-tag"
5962 | "no-progress"
5963 | "allow-tip-sha1-in-want"
5964 | "allow-reachable-sha1-in-want"
5965 | "filter"
5966 )
5967}
5968
5969fn reject_capability_value(label: &str, capability: &Capability) -> Result<()> {
5970 if capability.value.is_some() {
5971 return Err(GitError::InvalidFormat(format!(
5972 "{label} must not have value"
5973 )));
5974 }
5975 Ok(())
5976}
5977
5978fn is_known_receive_pack_capability(name: &str) -> bool {
5979 matches!(
5980 name,
5981 "report-status"
5982 | "report-status-v2"
5983 | "delete-refs"
5984 | "ofs-delta"
5985 | "atomic"
5986 | "push-options"
5987 | "side-band-64k"
5988 | "quiet"
5989 | "no-thin"
5990 | "agent"
5991 | "object-format"
5992 )
5993}
5994
5995fn is_receive_pack_delete_command(command: &ReceivePackCommand) -> bool {
5996 command.new_id.is_null()
5997}
5998
5999fn parse_receive_pack_unpack_status(payload: &[u8]) -> Result<ReceivePackUnpackStatus> {
6000 let text = parse_protocol_v2_line_text("receive-pack unpack status", payload)?;
6001 if text == "unpack ok" {
6002 return Ok(ReceivePackUnpackStatus::Ok);
6003 }
6004 let Some(message) = text.strip_prefix("unpack ") else {
6005 return Err(GitError::InvalidFormat(format!(
6006 "unsupported receive-pack unpack status {text}"
6007 )));
6008 };
6009 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6010 Ok(ReceivePackUnpackStatus::Error(message.to_string()))
6011}
6012
6013fn format_receive_pack_unpack_status(status: &ReceivePackUnpackStatus) -> Result<String> {
6014 match status {
6015 ReceivePackUnpackStatus::Ok => Ok("unpack ok".into()),
6016 ReceivePackUnpackStatus::Error(message) => {
6017 validate_receive_pack_status_message("receive-pack unpack error", message)?;
6018 Ok(format!("unpack {message}"))
6019 }
6020 }
6021}
6022
6023fn parse_receive_pack_command_status(payload: &[u8]) -> Result<ReceivePackCommandStatus> {
6024 let text = parse_protocol_v2_line_text("receive-pack command status", payload)?;
6025 if let Some(name) = text.strip_prefix("ok ") {
6026 validate_protocol_v2_token("receive-pack status ref name", name)?;
6027 return Ok(ReceivePackCommandStatus::Ok {
6028 name: name.to_string(),
6029 });
6030 }
6031 let Some(rest) = text.strip_prefix("ng ") else {
6032 return Err(GitError::InvalidFormat(format!(
6033 "unsupported receive-pack command status {text}"
6034 )));
6035 };
6036 let (name, message) = rest
6037 .split_once(' ')
6038 .ok_or_else(|| GitError::InvalidFormat("receive-pack ng status missing message".into()))?;
6039 validate_protocol_v2_token("receive-pack status ref name", name)?;
6040 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6041 Ok(ReceivePackCommandStatus::Ng {
6042 name: name.to_string(),
6043 message: message.to_string(),
6044 })
6045}
6046
6047fn format_receive_pack_command_status(status: &ReceivePackCommandStatus) -> Result<String> {
6048 match status {
6049 ReceivePackCommandStatus::Ok { name } => {
6050 validate_protocol_v2_token("receive-pack status ref name", name)?;
6051 Ok(format!("ok {name}"))
6052 }
6053 ReceivePackCommandStatus::Ng { name, message } => {
6054 validate_protocol_v2_token("receive-pack status ref name", name)?;
6055 validate_receive_pack_status_message("receive-pack ng status message", message)?;
6056 Ok(format!("ng {name} {message}"))
6057 }
6058 }
6059}
6060
6061fn parse_receive_pack_command_status_v2(text: &str) -> Result<ReceivePackCommandStatusV2> {
6062 if let Some(name) = text.strip_prefix("ok ") {
6063 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6064 return Ok(ReceivePackCommandStatusV2::Ok {
6065 name: name.to_string(),
6066 options: ReceivePackCommandStatusV2Options::default(),
6067 });
6068 }
6069 let Some(rest) = text.strip_prefix("ng ") else {
6070 return Err(GitError::InvalidFormat(format!(
6071 "unsupported receive-pack report-status-v2 line {text}"
6072 )));
6073 };
6074 let (name, message) = rest.split_once(' ').ok_or_else(|| {
6075 GitError::InvalidFormat("receive-pack status-v2 ng status missing message".into())
6076 })?;
6077 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6078 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6079 Ok(ReceivePackCommandStatusV2::Ng {
6080 name: name.to_string(),
6081 message: message.to_string(),
6082 })
6083}
6084
6085fn format_receive_pack_command_status_v2(status: &ReceivePackCommandStatusV2) -> Result<String> {
6086 match status {
6087 ReceivePackCommandStatusV2::Ok { name, .. } => {
6088 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6089 Ok(format!("ok {name}"))
6090 }
6091 ReceivePackCommandStatusV2::Ng { name, message } => {
6092 validate_protocol_v2_token("receive-pack status-v2 ref name", name)?;
6093 validate_receive_pack_status_message("receive-pack status-v2 ng message", message)?;
6094 Ok(format!("ng {name} {message}"))
6095 }
6096 }
6097}
6098
6099fn parse_receive_pack_report_status_v2_option(
6100 format: ObjectFormat,
6101 text: &str,
6102 options: &mut ReceivePackCommandStatusV2Options,
6103) -> Result<()> {
6104 if let Some(refname) = text.strip_prefix("option refname ") {
6105 if options.refname.is_some() {
6106 return Err(GitError::InvalidFormat(
6107 "receive-pack report-status-v2 has duplicate refname option".into(),
6108 ));
6109 }
6110 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6111 options.refname = Some(refname.to_string());
6112 } else if let Some(old_oid) = text.strip_prefix("option old-oid ") {
6113 if options.old_oid.is_some() {
6114 return Err(GitError::InvalidFormat(
6115 "receive-pack report-status-v2 has duplicate old-oid option".into(),
6116 ));
6117 }
6118 validate_protocol_v2_token("receive-pack status-v2 option old-oid", old_oid)?;
6119 options.old_oid = Some(ObjectId::from_hex(format, old_oid)?);
6120 } else if let Some(new_oid) = text.strip_prefix("option new-oid ") {
6121 if options.new_oid.is_some() {
6122 return Err(GitError::InvalidFormat(
6123 "receive-pack report-status-v2 has duplicate new-oid option".into(),
6124 ));
6125 }
6126 validate_protocol_v2_token("receive-pack status-v2 option new-oid", new_oid)?;
6127 options.new_oid = Some(ObjectId::from_hex(format, new_oid)?);
6128 } else if text == "option forced-update" {
6129 if options.forced_update {
6130 return Err(GitError::InvalidFormat(
6131 "receive-pack report-status-v2 has duplicate forced-update option".into(),
6132 ));
6133 }
6134 options.forced_update = true;
6135 } else {
6136 return Err(GitError::InvalidFormat(format!(
6137 "unsupported receive-pack report-status-v2 option {text}"
6138 )));
6139 }
6140 Ok(())
6141}
6142
6143fn format_receive_pack_report_status_v2_options(
6144 options: &ReceivePackCommandStatusV2Options,
6145) -> Result<Vec<String>> {
6146 let mut out = Vec::new();
6147 if let Some(refname) = &options.refname {
6148 validate_protocol_v2_token("receive-pack status-v2 option refname", refname)?;
6149 out.push(format!("option refname {refname}"));
6150 }
6151 if let Some(old_oid) = &options.old_oid {
6152 out.push(format!("option old-oid {old_oid}"));
6153 }
6154 if let Some(new_oid) = &options.new_oid {
6155 out.push(format!("option new-oid {new_oid}"));
6156 }
6157 if options.forced_update {
6158 out.push("option forced-update".into());
6159 }
6160 Ok(out)
6161}
6162
6163fn validate_receive_pack_status_message(label: &str, message: &str) -> Result<()> {
6164 if message.is_empty() {
6165 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6166 }
6167 if message
6168 .bytes()
6169 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6170 {
6171 return Err(GitError::InvalidFormat(format!(
6172 "{label} contains a delimiter byte"
6173 )));
6174 }
6175 Ok(())
6176}
6177
6178fn validate_receive_pack_push_option(option: &[u8]) -> Result<()> {
6179 if option.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6180 return Err(GitError::InvalidFormat(
6181 "receive-pack push-option contains a delimiter byte".into(),
6182 ));
6183 }
6184 Ok(())
6185}
6186
6187fn validate_protocol_error_message(message: &str) -> Result<()> {
6188 if message.is_empty() {
6189 return Err(GitError::InvalidFormat(
6190 "protocol error message is empty".into(),
6191 ));
6192 }
6193 if message
6194 .bytes()
6195 .any(|byte| matches!(byte, b'\n' | b'\r' | 0))
6196 {
6197 return Err(GitError::InvalidFormat(
6198 "protocol error message contains a delimiter byte".into(),
6199 ));
6200 }
6201 Ok(())
6202}
6203
6204fn parse_capability_token(token: &str) -> Result<Capability> {
6205 if token.is_empty() {
6206 return Err(GitError::InvalidFormat("empty capability token".into()));
6207 }
6208 let (name, value) = token
6209 .split_once('=')
6210 .map_or((token, None), |(name, value)| (name, Some(value)));
6211 validate_capability_field("capability name", name)?;
6212 if let Some(value) = value {
6213 validate_capability_field("capability value", value)?;
6214 }
6215 Ok(Capability {
6216 name: name.to_string(),
6217 value: value.map(str::to_string),
6218 })
6219}
6220
6221fn parse_protocol_v2_capability_line(payload: &[u8]) -> Result<Capability> {
6222 let payload = trim_trailing_lf(payload);
6223 if payload.is_empty() {
6224 return Err(GitError::InvalidFormat(
6225 "empty protocol v2 capability line".into(),
6226 ));
6227 }
6228 let text =
6229 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6230 let (name, value) = text
6231 .split_once('=')
6232 .map_or((text, None), |(name, value)| (name, Some(value)));
6233 validate_capability_name(name)?;
6234 if let Some(value) = value {
6235 validate_protocol_v2_capability_value(value)?;
6236 }
6237 Ok(Capability {
6238 name: name.to_string(),
6239 value: value.map(str::to_string),
6240 })
6241}
6242
6243fn parse_protocol_v2_command_line(payload: &[u8]) -> Result<String> {
6244 let payload = trim_trailing_lf(payload);
6245 let text =
6246 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6247 let Some(command) = text.strip_prefix("command=") else {
6248 return Err(GitError::InvalidFormat(
6249 "protocol v2 command request missing command prefix".into(),
6250 ));
6251 };
6252 validate_capability_name(command)?;
6253 Ok(command.to_string())
6254}
6255
6256fn parse_protocol_v2_ls_refs_line(
6257 format: ObjectFormat,
6258 payload: &[u8],
6259) -> Result<ProtocolV2LsRefsRecord> {
6260 let payload = trim_trailing_lf(payload);
6261 if payload.is_empty() {
6262 return Err(GitError::InvalidFormat(
6263 "ls-refs response line is empty".into(),
6264 ));
6265 }
6266 let text =
6267 std::str::from_utf8(payload).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6268 let mut fields = text.split(' ');
6269 let first = fields
6270 .next()
6271 .ok_or_else(|| GitError::InvalidFormat("ls-refs response line is empty".into()))?;
6272 if first == "unborn" {
6273 let name = fields
6274 .next()
6275 .ok_or_else(|| GitError::InvalidFormat("ls-refs unborn line is missing name".into()))?;
6276 validate_protocol_v2_token("ls-refs ref name", name)?;
6277 let (symref_target, attributes) = parse_protocol_v2_ls_refs_attributes(format, fields)?;
6278 return Ok(ProtocolV2LsRefsRecord::Unborn {
6279 name: name.to_string(),
6280 symref_target,
6281 attributes,
6282 });
6283 }
6284
6285 let oid = ObjectId::from_hex(format, first)?;
6286 let name = fields
6287 .next()
6288 .ok_or_else(|| GitError::InvalidFormat("ls-refs ref line is missing name".into()))?;
6289 validate_protocol_v2_token("ls-refs ref name", name)?;
6290 let (peeled, symref_target, attributes) =
6291 parse_protocol_v2_ls_refs_ref_attributes(format, fields)?;
6292 Ok(ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
6293 oid,
6294 name: name.to_string(),
6295 peeled,
6296 symref_target,
6297 attributes,
6298 }))
6299}
6300
6301fn parse_protocol_v2_ls_refs_ref_attributes<'a>(
6302 format: ObjectFormat,
6303 fields: impl Iterator<Item = &'a str>,
6304) -> Result<(Option<ObjectId>, Option<String>, Vec<String>)> {
6305 let mut peeled = None;
6306 let (symref_target, attributes) =
6307 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6308 if let Some(value) = attr.strip_prefix("peeled:") {
6309 if peeled.is_some() {
6310 return Err(GitError::InvalidFormat(
6311 "ls-refs response has duplicate peeled attribute".into(),
6312 ));
6313 }
6314 peeled = Some(ObjectId::from_hex(format, value)?);
6315 return Ok(true);
6316 }
6317 Ok(false)
6318 })?;
6319 Ok((peeled, symref_target, attributes))
6320}
6321
6322fn parse_protocol_v2_ls_refs_attributes<'a>(
6323 format: ObjectFormat,
6324 fields: impl Iterator<Item = &'a str>,
6325) -> Result<(Option<String>, Vec<String>)> {
6326 parse_protocol_v2_ls_refs_attributes_with(format, fields, |attr| {
6327 if attr.starts_with("peeled:") {
6328 return Err(GitError::InvalidFormat(
6329 "ls-refs unborn line has peeled attribute".into(),
6330 ));
6331 }
6332 Ok(false)
6333 })
6334}
6335
6336fn parse_protocol_v2_ls_refs_attributes_with<'a, F>(
6337 _format: ObjectFormat,
6338 fields: impl Iterator<Item = &'a str>,
6339 mut handle_known: F,
6340) -> Result<(Option<String>, Vec<String>)>
6341where
6342 F: FnMut(&str) -> Result<bool>,
6343{
6344 let mut symref_target = None;
6345 let mut attributes = Vec::new();
6346 for attr in fields {
6347 validate_protocol_v2_token("ls-refs attribute", attr)?;
6348 if let Some(value) = attr.strip_prefix("symref-target:") {
6349 if symref_target.is_some() {
6350 return Err(GitError::InvalidFormat(
6351 "ls-refs response has duplicate symref-target attribute".into(),
6352 ));
6353 }
6354 validate_protocol_v2_token("ls-refs symref-target", value)?;
6355 symref_target = Some(value.to_string());
6356 } else if !handle_known(attr)? {
6357 attributes.push(attr.to_string());
6358 }
6359 }
6360 Ok((symref_target, attributes))
6361}
6362
6363fn format_protocol_v2_ls_refs_record(record: &ProtocolV2LsRefsRecord) -> Result<String> {
6364 let mut out = String::new();
6365 match record {
6366 ProtocolV2LsRefsRecord::Ref(reference) => {
6367 validate_protocol_v2_token("ls-refs ref name", &reference.name)?;
6368 out.push_str(&reference.oid.to_string());
6369 out.push(' ');
6370 out.push_str(&reference.name);
6371 if let Some(peeled) = &reference.peeled {
6372 if peeled.format() != reference.oid.format() {
6373 return Err(GitError::InvalidObjectId(
6374 "ls-refs peeled object format does not match ref object format".into(),
6375 ));
6376 }
6377 out.push(' ');
6378 out.push_str("peeled:");
6379 out.push_str(&peeled.to_string());
6380 }
6381 if let Some(target) = &reference.symref_target {
6382 validate_protocol_v2_token("ls-refs symref-target", target)?;
6383 out.push(' ');
6384 out.push_str("symref-target:");
6385 out.push_str(target);
6386 }
6387 append_protocol_v2_ls_refs_attributes(&mut out, &reference.attributes)?;
6388 }
6389 ProtocolV2LsRefsRecord::Unborn {
6390 name,
6391 symref_target,
6392 attributes,
6393 } => {
6394 validate_protocol_v2_token("ls-refs ref name", name)?;
6395 out.push_str("unborn ");
6396 out.push_str(name);
6397 if let Some(target) = symref_target {
6398 validate_protocol_v2_token("ls-refs symref-target", target)?;
6399 out.push(' ');
6400 out.push_str("symref-target:");
6401 out.push_str(target);
6402 }
6403 append_protocol_v2_ls_refs_attributes(&mut out, attributes)?;
6404 }
6405 }
6406 Ok(out)
6407}
6408
6409fn append_protocol_v2_ls_refs_attributes(out: &mut String, attributes: &[String]) -> Result<()> {
6410 for attr in attributes {
6411 validate_protocol_v2_token("ls-refs attribute", attr)?;
6412 if attr.starts_with("peeled:") || attr.starts_with("symref-target:") {
6413 return Err(GitError::InvalidFormat(
6414 "ls-refs generic attributes must not duplicate known attributes".into(),
6415 ));
6416 }
6417 out.push(' ');
6418 out.push_str(attr);
6419 }
6420 Ok(())
6421}
6422
6423fn parse_fetch_section_header(payload: &[u8]) -> Result<String> {
6424 let name = parse_protocol_v2_line_text("fetch response section", payload)?;
6425 validate_capability_name(name)?;
6426 Ok(name.to_string())
6427}
6428
6429fn flush_terminates_protocol_v2_response(frames: &[PktLineFrame], idx: usize) -> bool {
6430 idx + 1 == frames.len()
6431 || (idx + 2 == frames.len() && matches!(frames[idx + 1], PktLineFrame::ResponseEnd))
6432}
6433
6434fn parse_fetch_section(
6435 format: ObjectFormat,
6436 name: String,
6437 lines: Vec<Vec<u8>>,
6438) -> Result<ProtocolV2FetchResponseSection> {
6439 match name.as_str() {
6440 "acknowledgments" => lines
6441 .iter()
6442 .map(|line| parse_fetch_acknowledgment(format, line))
6443 .collect::<Result<Vec<_>>>()
6444 .map(ProtocolV2FetchResponseSection::Acknowledgments),
6445 "shallow-info" => lines
6446 .iter()
6447 .map(|line| parse_fetch_shallow_info(format, line))
6448 .collect::<Result<Vec<_>>>()
6449 .map(ProtocolV2FetchResponseSection::ShallowInfo),
6450 "wanted-refs" => lines
6451 .iter()
6452 .map(|line| parse_fetch_wanted_ref(format, line))
6453 .collect::<Result<Vec<_>>>()
6454 .map(ProtocolV2FetchResponseSection::WantedRefs),
6455 "packfile-uris" => lines
6456 .iter()
6457 .map(|line| parse_fetch_packfile_uri(format, line))
6458 .collect::<Result<Vec<_>>>()
6459 .map(ProtocolV2FetchResponseSection::PackfileUris),
6460 "packfile" => Ok(ProtocolV2FetchResponseSection::Packfile(lines)),
6461 _ => Ok(ProtocolV2FetchResponseSection::Unknown { name, lines }),
6462 }
6463}
6464
6465fn parse_fetch_acknowledgment(
6466 format: ObjectFormat,
6467 line: &[u8],
6468) -> Result<ProtocolV2FetchAcknowledgment> {
6469 let text = parse_protocol_v2_line_text("fetch acknowledgment", line)?;
6470 match text {
6471 "NAK" => Ok(ProtocolV2FetchAcknowledgment::Nak),
6472 "ready" => Ok(ProtocolV2FetchAcknowledgment::Ready),
6473 value if value.starts_with("ACK ") => Ok(ProtocolV2FetchAcknowledgment::Ack(
6474 parse_oid_argument(format, "fetch ACK", value, "ACK ")?,
6475 )),
6476 other => Err(GitError::InvalidFormat(format!(
6477 "unsupported fetch acknowledgment {other}"
6478 ))),
6479 }
6480}
6481
6482fn parse_fetch_shallow_info(
6483 format: ObjectFormat,
6484 line: &[u8],
6485) -> Result<ProtocolV2FetchShallowInfo> {
6486 let text = parse_protocol_v2_line_text("fetch shallow-info", line)?;
6487 if text.starts_with("shallow ") {
6488 return Ok(ProtocolV2FetchShallowInfo::Shallow(parse_oid_argument(
6489 format,
6490 "fetch shallow",
6491 text,
6492 "shallow ",
6493 )?));
6494 }
6495 if text.starts_with("unshallow ") {
6496 return Ok(ProtocolV2FetchShallowInfo::Unshallow(parse_oid_argument(
6497 format,
6498 "fetch unshallow",
6499 text,
6500 "unshallow ",
6501 )?));
6502 }
6503 Err(GitError::InvalidFormat(format!(
6504 "unsupported fetch shallow-info {text}"
6505 )))
6506}
6507
6508fn parse_fetch_wanted_ref(format: ObjectFormat, line: &[u8]) -> Result<ProtocolV2FetchWantedRef> {
6509 let text = parse_protocol_v2_line_text("fetch wanted-ref", line)?;
6510 let (oid, name) = text
6511 .split_once(' ')
6512 .ok_or_else(|| GitError::InvalidFormat("fetch wanted-ref is missing name".into()))?;
6513 validate_protocol_v2_token("fetch wanted-ref name", name)?;
6514 Ok(ProtocolV2FetchWantedRef {
6515 oid: ObjectId::from_hex(format, oid)?,
6516 name: name.to_string(),
6517 })
6518}
6519
6520fn parse_fetch_packfile_uri(
6521 format: ObjectFormat,
6522 line: &[u8],
6523) -> Result<ProtocolV2FetchPackfileUri> {
6524 let text = parse_protocol_v2_line_text("fetch packfile-uri", line)?;
6525 let (pack_hash, uri) = text
6526 .split_once(' ')
6527 .ok_or_else(|| GitError::InvalidFormat("fetch packfile-uri is missing uri".into()))?;
6528 validate_protocol_v2_token("fetch packfile-uri hash", pack_hash)?;
6529 validate_protocol_v2_token("fetch packfile-uri", uri)?;
6530 Ok(ProtocolV2FetchPackfileUri {
6531 pack_hash: ObjectId::from_hex(format, pack_hash)?,
6532 uri: uri.to_string(),
6533 })
6534}
6535
6536fn protocol_v2_fetch_section_name(section: &ProtocolV2FetchResponseSection) -> &str {
6537 match section {
6538 ProtocolV2FetchResponseSection::Acknowledgments(_) => "acknowledgments",
6539 ProtocolV2FetchResponseSection::ShallowInfo(_) => "shallow-info",
6540 ProtocolV2FetchResponseSection::WantedRefs(_) => "wanted-refs",
6541 ProtocolV2FetchResponseSection::PackfileUris(_) => "packfile-uris",
6542 ProtocolV2FetchResponseSection::Packfile(_) => "packfile",
6543 ProtocolV2FetchResponseSection::Unknown { name, .. } => name,
6544 }
6545}
6546
6547fn format_protocol_v2_fetch_section_lines(
6548 section: &ProtocolV2FetchResponseSection,
6549) -> Result<Vec<Vec<u8>>> {
6550 match section {
6551 ProtocolV2FetchResponseSection::Acknowledgments(acks) => acks
6552 .iter()
6553 .map(|ack| match ack {
6554 ProtocolV2FetchAcknowledgment::Nak => Ok(line_from_str("NAK")),
6555 ProtocolV2FetchAcknowledgment::Ack(oid) => Ok(line_from_str(&format!("ACK {oid}"))),
6556 ProtocolV2FetchAcknowledgment::Ready => Ok(line_from_str("ready")),
6557 })
6558 .collect(),
6559 ProtocolV2FetchResponseSection::ShallowInfo(entries) => entries
6560 .iter()
6561 .map(|entry| match entry {
6562 ProtocolV2FetchShallowInfo::Shallow(oid) => {
6563 Ok(line_from_str(&format!("shallow {oid}")))
6564 }
6565 ProtocolV2FetchShallowInfo::Unshallow(oid) => {
6566 Ok(line_from_str(&format!("unshallow {oid}")))
6567 }
6568 })
6569 .collect(),
6570 ProtocolV2FetchResponseSection::WantedRefs(refs) => refs
6571 .iter()
6572 .map(|wanted| {
6573 validate_protocol_v2_token("fetch wanted-ref name", &wanted.name)?;
6574 Ok(line_from_str(&format!("{} {}", wanted.oid, wanted.name)))
6575 })
6576 .collect(),
6577 ProtocolV2FetchResponseSection::PackfileUris(uris) => uris
6578 .iter()
6579 .map(|packfile_uri| {
6580 validate_protocol_v2_token("fetch packfile-uri", &packfile_uri.uri)?;
6581 Ok(line_from_str(&format!(
6582 "{} {}",
6583 packfile_uri.pack_hash, packfile_uri.uri
6584 )))
6585 })
6586 .collect(),
6587 ProtocolV2FetchResponseSection::Packfile(lines) => Ok(lines.clone()),
6588 ProtocolV2FetchResponseSection::Unknown { name, lines } => {
6589 validate_capability_name(name)?;
6590 for line in lines {
6591 validate_protocol_v2_line("fetch unknown section line", line)?;
6592 }
6593 Ok(lines.clone())
6594 }
6595 }
6596}
6597
6598fn parse_protocol_v2_object_info_record(
6599 format: ObjectFormat,
6600 line: &[u8],
6601) -> Result<ProtocolV2ObjectInfoRecord> {
6602 let text = parse_protocol_v2_line_text("object-info record", line)?;
6603 let mut fields = text.split(' ');
6604 let oid = fields
6605 .next()
6606 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing oid".into()))?;
6607 let size = fields
6608 .next()
6609 .ok_or_else(|| GitError::InvalidFormat("object-info record is missing size".into()))?;
6610 if fields.next().is_some() {
6611 return Err(GitError::InvalidFormat(
6612 "object-info record has too many fields".into(),
6613 ));
6614 }
6615 validate_protocol_v2_token("object-info oid", oid)?;
6616 validate_protocol_v2_token("object-info size", size)?;
6617 let size = size
6618 .parse::<u64>()
6619 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6620 Ok(ProtocolV2ObjectInfoRecord {
6621 oid: ObjectId::from_hex(format, oid)?,
6622 size,
6623 })
6624}
6625
6626fn parse_dumb_http_info_ref_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpRefRecord> {
6627 validate_dumb_http_info_ref_line(line)?;
6628 let line = trim_trailing_lf(line);
6629 let tab = line
6630 .iter()
6631 .position(|byte| *byte == b'\t')
6632 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP ref record is missing name".into()))?;
6633 let (oid, name) = (&line[..tab], &line[tab + 1..]);
6634 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6635 validate_protocol_v2_token("dumb HTTP ref oid", oid)?;
6636 let name = std::str::from_utf8(name).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6637 let (name, peeled) = name
6638 .strip_suffix("^{}")
6639 .map_or((name, false), |name| (name, true));
6640 validate_dumb_http_ref_name(name)?;
6641 Ok(DumbHttpRefRecord {
6642 oid: ObjectId::from_hex(format, oid)?,
6643 name: name.to_string(),
6644 peeled,
6645 })
6646}
6647
6648fn parse_dumb_http_alternate(line: &[u8]) -> Result<String> {
6649 validate_dumb_http_alternate_line(line)?;
6650 let line = trim_trailing_lf(line);
6651 let alternate =
6652 std::str::from_utf8(line).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6653 validate_dumb_http_alternate(alternate)?;
6654 Ok(alternate.to_string())
6655}
6656
6657fn parse_dumb_http_pack_record(format: ObjectFormat, line: &[u8]) -> Result<DumbHttpPackRecord> {
6658 validate_dumb_http_info_ref_line(line)?;
6659 let line = parse_protocol_v2_line_text("dumb HTTP pack record", line)?;
6660 let pack_name = line
6661 .strip_prefix("P ")
6662 .ok_or_else(|| GitError::InvalidFormat("dumb HTTP pack record must start with P".into()))?;
6663 let hash = pack_name
6664 .strip_prefix("pack-")
6665 .and_then(|value| value.strip_suffix(".pack"))
6666 .ok_or_else(|| GitError::InvalidFormat("invalid dumb HTTP pack name".into()))?;
6667 validate_protocol_v2_token("dumb HTTP pack hash", hash)?;
6668 Ok(DumbHttpPackRecord {
6669 hash: ObjectId::from_hex(format, hash)?,
6670 })
6671}
6672
6673fn encode_protocol_v2_capability(capability: &Capability) -> Result<Vec<u8>> {
6674 validate_capability_name(&capability.name)?;
6675 let mut out = capability.name.as_bytes().to_vec();
6676 if let Some(value) = &capability.value {
6677 validate_protocol_v2_capability_value(value)?;
6678 out.push(b'=');
6679 out.extend_from_slice(value.as_bytes());
6680 }
6681 Ok(out)
6682}
6683
6684fn validate_capability_field(label: &str, value: &str) -> Result<()> {
6685 if value.is_empty() {
6686 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6687 }
6688 if value
6689 .bytes()
6690 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | b'\t' | 0))
6691 {
6692 return Err(GitError::InvalidFormat(format!(
6693 "{label} contains a delimiter byte"
6694 )));
6695 }
6696 Ok(())
6697}
6698
6699fn validate_capability_name(value: &str) -> Result<()> {
6700 validate_capability_field("capability name", value)?;
6701 if value.bytes().any(|byte| byte == b'=') {
6702 return Err(GitError::InvalidFormat(
6703 "capability name contains a delimiter byte".into(),
6704 ));
6705 }
6706 Ok(())
6707}
6708
6709fn validate_protocol_v2_capability_value(value: &str) -> Result<()> {
6710 if value.is_empty() {
6711 return Err(GitError::InvalidFormat(
6712 "protocol v2 capability value is empty".into(),
6713 ));
6714 }
6715 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6716 return Err(GitError::InvalidFormat(
6717 "protocol v2 capability value contains a delimiter byte".into(),
6718 ));
6719 }
6720 Ok(())
6721}
6722
6723fn validate_protocol_v2_argument(value: &[u8]) -> Result<()> {
6724 if value.is_empty() {
6725 return Err(GitError::InvalidFormat(
6726 "protocol v2 command argument is empty".into(),
6727 ));
6728 }
6729 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
6730 return Err(GitError::InvalidFormat(
6731 "protocol v2 command argument contains a delimiter byte".into(),
6732 ));
6733 }
6734 Ok(())
6735}
6736
6737fn validate_upload_archive_argument(value: &str) -> Result<()> {
6738 if value.is_empty() {
6739 return Err(GitError::InvalidFormat(
6740 "upload-archive argument is empty".into(),
6741 ));
6742 }
6743 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6744 return Err(GitError::InvalidFormat(
6745 "upload-archive argument contains a delimiter byte".into(),
6746 ));
6747 }
6748 Ok(())
6749}
6750
6751fn validate_upload_archive_status_message(value: &str) -> Result<()> {
6752 if value.is_empty() {
6753 return Err(GitError::InvalidFormat(
6754 "upload-archive status message is empty".into(),
6755 ));
6756 }
6757 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6758 return Err(GitError::InvalidFormat(
6759 "upload-archive status message contains a delimiter byte".into(),
6760 ));
6761 }
6762 Ok(())
6763}
6764
6765fn non_empty(value: &str) -> Option<&str> {
6766 (!value.is_empty()).then_some(value)
6767}
6768
6769fn validate_refspec_value(value: &str) -> Result<()> {
6770 if value.is_empty() {
6771 return Err(GitError::InvalidFormat("refspec is empty".into()));
6772 }
6773 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6774 return Err(GitError::InvalidFormat(
6775 "refspec contains a delimiter byte".into(),
6776 ));
6777 }
6778 Ok(())
6779}
6780
6781fn validate_refspec_endpoint(label: &str, value: &str) -> Result<()> {
6782 if value.is_empty() {
6783 return Err(GitError::InvalidFormat(format!("{label} is empty")));
6784 }
6785 if value
6786 .bytes()
6787 .any(|byte| matches!(byte, b':' | b' ' | b'\t' | b'\n' | b'\r' | 0))
6788 {
6789 return Err(GitError::InvalidFormat(format!(
6790 "{label} contains a delimiter byte"
6791 )));
6792 }
6793 Ok(())
6794}
6795
6796fn count_refspec_wildcards(value: &str) -> usize {
6797 value.bytes().filter(|byte| *byte == b'*').count()
6798}
6799
6800fn validate_refspec_shape(refspec: &RefSpec) -> Result<()> {
6801 if refspec.force && refspec.negative {
6802 return Err(GitError::InvalidFormat(
6803 "negative refspec must not be forced".into(),
6804 ));
6805 }
6806 if refspec.negative && refspec.dst.is_some() {
6807 return Err(GitError::InvalidFormat(
6808 "negative refspec must not have a destination".into(),
6809 ));
6810 }
6811 if refspec.negative && refspec.src.is_none() {
6812 return Err(GitError::InvalidFormat(
6813 "negative refspec is missing a source".into(),
6814 ));
6815 }
6816 if refspec.src.is_none() && refspec.dst.is_none() && refspec.negative {
6817 return Err(GitError::InvalidFormat(
6818 "refspec must include a source or destination".into(),
6819 ));
6820 }
6821 if let Some(src) = &refspec.src {
6822 validate_refspec_endpoint("refspec source", src)?;
6823 }
6824 if let Some(dst) = &refspec.dst {
6825 validate_refspec_endpoint("refspec destination", dst)?;
6826 }
6827 let src_pattern_count = refspec
6828 .src
6829 .as_deref()
6830 .map(count_refspec_wildcards)
6831 .unwrap_or(0);
6832 let dst_pattern_count = refspec
6833 .dst
6834 .as_deref()
6835 .map(count_refspec_wildcards)
6836 .unwrap_or(0);
6837 if src_pattern_count > 1 || dst_pattern_count > 1 {
6838 return Err(GitError::InvalidFormat(
6839 "refspec endpoint has too many wildcards".into(),
6840 ));
6841 }
6842 if refspec.dst.is_some() && (src_pattern_count == 1) != (dst_pattern_count == 1) {
6843 return Err(GitError::InvalidFormat(
6844 "refspec wildcard must appear in both source and destination".into(),
6845 ));
6846 }
6847 if refspec.pattern != (src_pattern_count == 1 || dst_pattern_count == 1) {
6848 return Err(GitError::InvalidFormat(
6849 "refspec pattern flag does not match endpoints".into(),
6850 ));
6851 }
6852 Ok(())
6853}
6854
6855fn parse_fetch_head_record(format: ObjectFormat, line: &[u8]) -> Result<FetchHeadRecord> {
6856 validate_fetch_head_line(line)?;
6857 let line = trim_trailing_lf(line);
6858 let mut fields = line.splitn(3, |byte| *byte == b'\t');
6859 let oid = fields
6860 .next()
6861 .ok_or_else(|| GitError::InvalidFormat("FETCH_HEAD record is missing oid".into()))?;
6862 let merge_marker = fields.next().ok_or_else(|| {
6863 GitError::InvalidFormat("FETCH_HEAD record is missing merge marker".into())
6864 })?;
6865 let description = fields.next().ok_or_else(|| {
6866 GitError::InvalidFormat("FETCH_HEAD record is missing description".into())
6867 })?;
6868 let oid = std::str::from_utf8(oid).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6869 validate_protocol_v2_token("FETCH_HEAD oid", oid)?;
6870 let not_for_merge = match merge_marker {
6871 b"" => false,
6872 b"not-for-merge" => true,
6873 _ => {
6874 return Err(GitError::InvalidFormat(
6875 "FETCH_HEAD record has invalid merge marker".into(),
6876 ));
6877 }
6878 };
6879 let description =
6880 std::str::from_utf8(description).map_err(|err| GitError::InvalidFormat(err.to_string()))?;
6881 validate_fetch_head_description_field(description)?;
6882 Ok(FetchHeadRecord {
6883 oid: ObjectId::from_hex(format, oid)?,
6884 not_for_merge,
6885 description: description.to_string(),
6886 })
6887}
6888
6889fn validate_fetch_head_line(value: &[u8]) -> Result<()> {
6890 if value.is_empty() {
6891 return Err(GitError::InvalidFormat("FETCH_HEAD record is empty".into()));
6892 }
6893 if !value.ends_with(b"\n") {
6894 return Err(GitError::InvalidFormat(
6895 "FETCH_HEAD record missing LF".into(),
6896 ));
6897 }
6898 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
6899 return Err(GitError::InvalidFormat(
6900 "FETCH_HEAD record contains a delimiter byte".into(),
6901 ));
6902 }
6903 Ok(())
6904}
6905
6906fn validate_fetch_head_description_field(value: &str) -> Result<()> {
6907 if value.is_empty() {
6908 return Err(GitError::InvalidFormat(
6909 "FETCH_HEAD description is empty".into(),
6910 ));
6911 }
6912 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
6913 return Err(GitError::InvalidFormat(
6914 "FETCH_HEAD description contains a delimiter byte".into(),
6915 ));
6916 }
6917 Ok(())
6918}
6919
6920fn refspec_is_excluded(negative: &[&RefSpec], source: &str) -> Result<bool> {
6921 for refspec in negative {
6922 if refspec_matches_source(refspec, source)? {
6923 return Ok(true);
6924 }
6925 }
6926 Ok(false)
6927}
6928
6929fn zero_object_id(format: ObjectFormat) -> Result<ObjectId> {
6930 Ok(ObjectId::null(format))
6931}
6932
6933fn local_ref<'a>(refs: &'a [PushSourceRef], name: &str) -> Option<&'a PushSourceRef> {
6934 refs.iter().find(|reference| reference.name == name)
6935}
6936
6937fn remote_ref<'a>(refs: &'a [RefAdvertisement], name: &str) -> Option<&'a RefAdvertisement> {
6938 refs.iter().find(|reference| reference.name == name)
6939}
6940
6941fn validate_push_source_ref(format: ObjectFormat, reference: &PushSourceRef) -> Result<()> {
6942 if reference.oid.format() != format {
6943 return Err(GitError::InvalidObjectId(
6944 "push source ref object format does not match repository".into(),
6945 ));
6946 }
6947 validate_refspec_endpoint("push source ref name", &reference.name)
6948}
6949
6950fn require_receive_pack_feature(advertised: bool, name: &str) -> Result<()> {
6951 if advertised {
6952 Ok(())
6953 } else {
6954 Err(GitError::InvalidFormat(format!(
6955 "receive-pack feature {name} was not advertised"
6956 )))
6957 }
6958}
6959
6960fn validate_smart_http_service(service: GitService) -> Result<()> {
6961 match service {
6962 GitService::UploadPack | GitService::ReceivePack => Ok(()),
6963 GitService::UploadArchive => Err(GitError::InvalidFormat(
6964 "smart HTTP only supports upload-pack and receive-pack services".into(),
6965 )),
6966 }
6967}
6968
6969fn normalize_http_repository_path(path: &str) -> Result<String> {
6970 if path.is_empty() {
6971 return Err(GitError::InvalidFormat(
6972 "smart HTTP repository path is empty".into(),
6973 ));
6974 }
6975 if !path.starts_with('/') {
6976 return Err(GitError::InvalidFormat(
6977 "smart HTTP repository path must start with /".into(),
6978 ));
6979 }
6980 if path
6981 .bytes()
6982 .any(|byte| matches!(byte, b'\n' | b'\r' | 0 | b'?' | b'#'))
6983 {
6984 return Err(GitError::InvalidFormat(
6985 "smart HTTP repository path contains a delimiter byte".into(),
6986 ));
6987 }
6988 let normalized = path.trim_end_matches('/');
6989 Ok(if normalized.is_empty() {
6990 "/".into()
6991 } else {
6992 normalized.to_string()
6993 })
6994}
6995
6996fn dumb_http_pack_resource_path(
6997 repository_path: &str,
6998 hash: &ObjectId,
6999 suffix: &str,
7000) -> Result<String> {
7001 let repository_path = normalize_http_repository_path(repository_path)?;
7002 Ok(format!(
7003 "{repository_path}/objects/pack/pack-{hash}.{suffix}"
7004 ))
7005}
7006
7007fn parse_smart_http_content_type(value: &str, suffix: &str) -> Result<GitService> {
7008 let value = value.trim();
7009 if value.is_empty() {
7010 return Err(GitError::InvalidFormat(
7011 "smart HTTP content type is empty".into(),
7012 ));
7013 }
7014 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7015 return Err(GitError::InvalidFormat(
7016 "smart HTTP content type contains a delimiter byte".into(),
7017 ));
7018 }
7019 let value = value.to_ascii_lowercase();
7020 let service = value
7021 .strip_prefix("application/x-")
7022 .and_then(|value| value.strip_suffix(suffix))
7023 .ok_or_else(|| GitError::InvalidFormat("invalid smart HTTP content type".into()))?;
7024 let service = parse_git_service(service)?;
7025 validate_smart_http_service(service)?;
7026 Ok(service)
7027}
7028
7029fn validate_dumb_http_info_ref_line(value: &[u8]) -> Result<()> {
7030 if value.is_empty() {
7031 return Err(GitError::InvalidFormat(
7032 "dumb HTTP ref record is empty".into(),
7033 ));
7034 }
7035 if !value.ends_with(b"\n") {
7036 return Err(GitError::InvalidFormat(
7037 "dumb HTTP ref record missing LF".into(),
7038 ));
7039 }
7040 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7041 return Err(GitError::InvalidFormat(
7042 "dumb HTTP ref record contains a delimiter byte".into(),
7043 ));
7044 }
7045 Ok(())
7046}
7047
7048fn validate_dumb_http_ref_name(value: &str) -> Result<()> {
7049 validate_protocol_v2_token("dumb HTTP ref name", value)?;
7050 if value.ends_with("^{}") {
7051 return Err(GitError::InvalidFormat(
7052 "dumb HTTP ref name must not include peeled suffix".into(),
7053 ));
7054 }
7055 Ok(())
7056}
7057
7058fn validate_dumb_http_alternate_line(value: &[u8]) -> Result<()> {
7059 if value.is_empty() {
7060 return Err(GitError::InvalidFormat(
7061 "dumb HTTP alternate is empty".into(),
7062 ));
7063 }
7064 if !value.ends_with(b"\n") {
7065 return Err(GitError::InvalidFormat(
7066 "dumb HTTP alternate missing LF".into(),
7067 ));
7068 }
7069 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7070 return Err(GitError::InvalidFormat(
7071 "dumb HTTP alternate contains a delimiter byte".into(),
7072 ));
7073 }
7074 Ok(())
7075}
7076
7077fn validate_dumb_http_alternate(value: &str) -> Result<()> {
7078 if value.is_empty() {
7079 return Err(GitError::InvalidFormat(
7080 "dumb HTTP alternate is empty".into(),
7081 ));
7082 }
7083 if value.bytes().any(|byte| matches!(byte, b'\n' | b'\r' | 0)) {
7084 return Err(GitError::InvalidFormat(
7085 "dumb HTTP alternate contains a delimiter byte".into(),
7086 ));
7087 }
7088 Ok(())
7089}
7090
7091fn validate_protocol_v2_token(label: &str, value: &str) -> Result<()> {
7092 if value.is_empty() {
7093 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7094 }
7095 if value
7096 .bytes()
7097 .any(|byte| matches!(byte, b' ' | b'\n' | b'\r' | 0))
7098 {
7099 return Err(GitError::InvalidFormat(format!(
7100 "{label} contains a delimiter byte"
7101 )));
7102 }
7103 Ok(())
7104}
7105
7106fn validate_protocol_v2_line(label: &str, value: &[u8]) -> Result<()> {
7107 if value.is_empty() {
7108 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7109 }
7110 if value.iter().any(|byte| matches!(*byte, b'\r' | 0)) {
7111 return Err(GitError::InvalidFormat(format!(
7112 "{label} contains a delimiter byte"
7113 )));
7114 }
7115 Ok(())
7116}
7117
7118fn parse_protocol_v2_line_text<'a>(label: &str, value: &'a [u8]) -> Result<&'a str> {
7119 validate_protocol_v2_line(label, value)?;
7120 let value = trim_trailing_lf(value);
7121 if value.is_empty() {
7122 return Err(GitError::InvalidFormat(format!("{label} is empty")));
7123 }
7124 if value.iter().any(|byte| matches!(*byte, b'\n' | b'\r' | 0)) {
7125 return Err(GitError::InvalidFormat(format!(
7126 "{label} contains a delimiter byte"
7127 )));
7128 }
7129 std::str::from_utf8(value).map_err(|err| GitError::InvalidFormat(err.to_string()))
7130}
7131
7132fn parse_oid_argument(
7133 format: ObjectFormat,
7134 label: &str,
7135 value: &str,
7136 prefix: &str,
7137) -> Result<ObjectId> {
7138 let oid = value
7139 .strip_prefix(prefix)
7140 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7141 validate_protocol_v2_token(label, oid)?;
7142 ObjectId::from_hex(format, oid)
7143}
7144
7145fn parse_u32_argument(label: &str, value: &str, prefix: &str) -> Result<u32> {
7146 let number = value
7147 .strip_prefix(prefix)
7148 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7149 validate_protocol_v2_token(label, number)?;
7150 let parsed = number
7151 .parse::<u32>()
7152 .map_err(|err| GitError::InvalidFormat(err.to_string()))?;
7153 if parsed == 0 {
7154 return Err(GitError::InvalidFormat(format!("{label} must be positive")));
7155 }
7156 Ok(parsed)
7157}
7158
7159fn parse_u64_argument(label: &str, value: &str, prefix: &str) -> Result<u64> {
7160 let number = value
7161 .strip_prefix(prefix)
7162 .ok_or_else(|| GitError::InvalidFormat(format!("invalid {label}")))?;
7163 validate_protocol_v2_token(label, number)?;
7164 number
7165 .parse::<u64>()
7166 .map_err(|err| GitError::InvalidFormat(err.to_string()))
7167}
7168
7169fn line(mut payload: Vec<u8>) -> Vec<u8> {
7170 payload.push(b'\n');
7171 payload
7172}
7173
7174fn line_from_str(payload: &str) -> Vec<u8> {
7175 line(payload.as_bytes().to_vec())
7176}
7177
7178fn trim_trailing_lf(input: &[u8]) -> &[u8] {
7179 input.strip_suffix(b"\n").unwrap_or(input)
7180}
7181
7182#[cfg(test)]
7183mod tests {
7184 use super::*;
7185
7186 #[test]
7187 fn pkt_line_frame_encodes_data_and_control_frames() {
7188 assert_eq!(PktLine(b"hello\n".to_vec()).encode(), b"000ahello\n");
7189 assert_eq!(
7190 PktLineFrame::data(b"hello\n".to_vec())
7191 .expect("test operation should succeed")
7192 .encode(),
7193 b"000ahello\n"
7194 );
7195 assert_eq!(PktLineFrame::Flush.encode(), b"0000");
7196 assert_eq!(PktLineFrame::Delimiter.encode(), b"0001");
7197 assert_eq!(PktLineFrame::ResponseEnd.encode(), b"0002");
7198 assert_eq!(
7199 PktLineFrame::data(b"hello\n".to_vec())
7200 .expect("test operation should succeed")
7201 .try_encode()
7202 .expect("test operation should succeed"),
7203 b"000ahello\n"
7204 );
7205 }
7206
7207 #[test]
7208 fn pkt_line_frame_parses_data_and_control_frames() {
7209 assert_eq!(
7210 PktLineFrame::parse(b"000ahello\n").expect("test operation should succeed"),
7211 (PktLineFrame::Data(b"hello\n".to_vec()), 10)
7212 );
7213 assert_eq!(
7214 PktLineFrame::parse(b"0000").expect("test operation should succeed"),
7215 (PktLineFrame::Flush, 4)
7216 );
7217 assert_eq!(
7218 PktLineFrame::parse(b"0001").expect("test operation should succeed"),
7219 (PktLineFrame::Delimiter, 4)
7220 );
7221 assert_eq!(
7222 PktLineFrame::parse(b"0002").expect("test operation should succeed"),
7223 (PktLineFrame::ResponseEnd, 4)
7224 );
7225 }
7226
7227 #[test]
7228 fn pkt_line_stream_parses_multiple_frames() {
7229 let frames = parse_pkt_line_stream(b"000eversion 2\n00010009done\n0000")
7230 .expect("test operation should succeed");
7231 assert_eq!(
7232 frames,
7233 vec![
7234 PktLineFrame::Data(b"version 2\n".to_vec()),
7235 PktLineFrame::Delimiter,
7236 PktLineFrame::Data(b"done\n".to_vec()),
7237 PktLineFrame::Flush,
7238 ]
7239 );
7240 }
7241
7242 #[test]
7243 fn pkt_line_stream_reads_and_writes_incremental_io() {
7244 let frames = vec![
7245 PktLineFrame::Data(b"version 2\n".to_vec()),
7246 PktLineFrame::Delimiter,
7247 PktLineFrame::Data(b"done\n".to_vec()),
7248 PktLineFrame::Flush,
7249 ];
7250 let mut encoded = Vec::new();
7251 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
7252 assert_eq!(encoded, b"000eversion 2\n00010009done\n0000");
7253 assert_eq!(
7254 read_pkt_line_frames(&mut encoded.as_slice()).expect("test operation should succeed"),
7255 frames
7256 );
7257
7258 let mut empty: &[u8] = b"";
7259 assert_eq!(
7260 read_pkt_line_frame(&mut empty).expect("test operation should succeed"),
7261 None
7262 );
7263 }
7264
7265 #[test]
7266 fn pkt_line_stream_reads_until_control_packets() {
7267 let input = b"000eversion 2\n0000trailing";
7268 let frames = read_pkt_line_frames_until_flush(&mut &input[..])
7269 .expect("test operation should succeed");
7270 assert_eq!(
7271 frames,
7272 vec![
7273 PktLineFrame::Data(b"version 2\n".to_vec()),
7274 PktLineFrame::Flush,
7275 ]
7276 );
7277
7278 let input = b"0009done\n0002next";
7279 let frames = read_pkt_line_frames_until_response_end(&mut &input[..])
7280 .expect("test operation should succeed");
7281 assert_eq!(
7282 frames,
7283 vec![
7284 PktLineFrame::Data(b"done\n".to_vec()),
7285 PktLineFrame::ResponseEnd,
7286 ]
7287 );
7288 assert!(read_pkt_line_frames_until_flush(&mut &b"0009done\n"[..]).is_err());
7289 }
7290
7291 #[test]
7292 fn pkt_line_rejects_invalid_lengths() {
7293 assert!(PktLineFrame::parse(b"000").is_err());
7294 assert!(PktLineFrame::parse(b"0003").is_err());
7295 assert!(PktLineFrame::parse(b"000ahello").is_err());
7296 assert!(PktLineFrame::parse(b"zzzz").is_err());
7297 assert!(read_pkt_line_frame(&mut &b"000"[..]).is_err());
7298 assert!(read_pkt_line_frame(&mut &b"0003"[..]).is_err());
7299 }
7300
7301 #[test]
7302 fn pkt_line_rejects_oversized_data() {
7303 let payload = vec![b'x'; PKT_LINE_MAX_PAYLOAD_LEN + 1];
7304 assert!(PktLineFrame::data(payload.clone()).is_err());
7305 assert!(PktLine(payload.clone()).try_encode().is_err());
7306 assert!(PktLineFrame::Data(payload.clone()).try_encode().is_err());
7307 assert!(write_pkt_line_frame(&mut Vec::new(), &PktLineFrame::Data(payload)).is_err());
7308 assert!(PktLineFrame::parse(b"fff1").is_err());
7309 }
7310
7311 #[test]
7312 fn protocol_error_lines_parse_encode_and_stream() {
7313 let error = parse_error_line(b"ERR remote rejected request\n")
7314 .expect("test operation should succeed");
7315 assert_eq!(
7316 error,
7317 ProtocolErrorLine {
7318 message: "remote rejected request".into(),
7319 }
7320 );
7321 assert_eq!(
7322 encode_error_line(&error).expect("test operation should succeed"),
7323 b"ERR remote rejected request\n"
7324 );
7325 assert_eq!(
7326 parse_error_frame(&PktLineFrame::Data(
7327 b"ERR remote rejected request\n".to_vec()
7328 ))
7329 .expect("test operation should succeed"),
7330 Some(error.clone())
7331 );
7332 assert_eq!(
7333 parse_error_frame(&PktLineFrame::Data(b"NAK\n".to_vec()))
7334 .expect("test operation should succeed"),
7335 None
7336 );
7337
7338 let mut encoded = Vec::new();
7339 write_error_line(&mut encoded, &error).expect("test operation should succeed");
7340 encoded.extend_from_slice(b"tail");
7341 let mut input = encoded.as_slice();
7342 assert_eq!(
7343 read_error_line(&mut input).expect("test operation should succeed"),
7344 error
7345 );
7346 assert_eq!(input, b"tail");
7347 }
7348
7349 #[test]
7350 fn protocol_error_lines_reject_malformed_messages() {
7351 assert!(parse_error_line(b"ERR\n").is_err());
7352 assert!(parse_error_line(b"ERR \n").is_err());
7353 assert!(parse_error_line(b"ERR bad\0message\n").is_err());
7354 assert!(parse_error_line(b"NAK\n").is_err());
7355 assert!(
7356 encode_error_line(&ProtocolErrorLine {
7357 message: "bad\nmessage".into(),
7358 })
7359 .is_err()
7360 );
7361 assert!(read_error_line(&mut &b"0000"[..]).is_err());
7362 }
7363
7364 #[test]
7365 fn refspec_parser_handles_fetch_push_and_negative_forms() {
7366 assert_eq!(
7367 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7368 .expect("test operation should succeed"),
7369 RefSpec {
7370 force: true,
7371 negative: false,
7372 src: Some("refs/heads/*".into()),
7373 dst: Some("refs/remotes/origin/*".into()),
7374 pattern: true,
7375 }
7376 );
7377 assert_eq!(
7378 parse_refspec("refs/heads/main").expect("test operation should succeed"),
7379 RefSpec {
7380 force: false,
7381 negative: false,
7382 src: Some("refs/heads/main".into()),
7383 dst: None,
7384 pattern: false,
7385 }
7386 );
7387 assert_eq!(
7388 parse_refspec(":refs/heads/topic").expect("test operation should succeed"),
7389 RefSpec {
7390 force: false,
7391 negative: false,
7392 src: None,
7393 dst: Some("refs/heads/topic".into()),
7394 pattern: false,
7395 }
7396 );
7397 assert_eq!(
7398 parse_refspec(":").expect("test operation should succeed"),
7399 RefSpec {
7400 force: false,
7401 negative: false,
7402 src: None,
7403 dst: None,
7404 pattern: false,
7405 }
7406 );
7407 assert_eq!(
7408 parse_refspec("^refs/tags/private/*").expect("test operation should succeed"),
7409 RefSpec {
7410 force: false,
7411 negative: true,
7412 src: Some("refs/tags/private/*".into()),
7413 dst: None,
7414 pattern: true,
7415 }
7416 );
7417 }
7418
7419 #[test]
7420 fn refspec_encode_and_map_sources() {
7421 let pattern = parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7422 .expect("test operation should succeed");
7423 assert_eq!(
7424 encode_refspec(&pattern).expect("test operation should succeed"),
7425 "+refs/heads/*:refs/remotes/origin/*"
7426 );
7427 assert!(
7428 refspec_matches_source(&pattern, "refs/heads/main")
7429 .expect("test operation should succeed")
7430 );
7431 assert_eq!(
7432 refspec_map_source(&pattern, "refs/heads/main").expect("test operation should succeed"),
7433 Some("refs/remotes/origin/main".into())
7434 );
7435 assert_eq!(
7436 refspec_map_source(&pattern, "refs/tags/v1").expect("test operation should succeed"),
7437 None
7438 );
7439
7440 let direct = parse_refspec("HEAD:refs/heads/main").expect("test operation should succeed");
7441 assert_eq!(
7442 encode_refspec(&direct).expect("test operation should succeed"),
7443 "HEAD:refs/heads/main"
7444 );
7445 assert_eq!(
7446 refspec_map_source(&direct, "HEAD").expect("test operation should succeed"),
7447 Some("refs/heads/main".into())
7448 );
7449
7450 let delete = parse_refspec(":refs/heads/old").expect("test operation should succeed");
7451 assert_eq!(
7452 encode_refspec(&delete).expect("test operation should succeed"),
7453 ":refs/heads/old"
7454 );
7455 assert_eq!(
7456 refspec_map_source(&delete, "HEAD").expect("test operation should succeed"),
7457 None
7458 );
7459
7460 let matching = parse_refspec(":").expect("test operation should succeed");
7461 assert_eq!(
7462 encode_refspec(&matching).expect("test operation should succeed"),
7463 ":"
7464 );
7465 }
7466
7467 #[test]
7468 fn refspec_parser_rejects_malformed_values() {
7469 assert!(parse_refspec("").is_err());
7470 assert!(parse_refspec("+^refs/heads/main").is_err());
7471 assert!(parse_refspec("^refs/heads/main:refs/remotes/origin/main").is_err());
7472 assert!(parse_refspec("refs/heads/*:refs/remotes/origin/main").is_err());
7473 assert!(parse_refspec("refs/heads/**:refs/remotes/origin/*").is_err());
7474 assert!(parse_refspec("refs/heads/main:refs/remotes/origin/main:extra").is_err());
7475 assert!(parse_refspec("refs/heads/main\n").is_err());
7476 assert!(
7477 encode_refspec(&RefSpec {
7478 force: false,
7479 negative: false,
7480 src: Some("refs/heads/*".into()),
7481 dst: Some("refs/remotes/origin/main".into()),
7482 pattern: true,
7483 })
7484 .is_err()
7485 );
7486 }
7487
7488 #[test]
7489 fn fetch_head_records_parse_encode_and_describe_refs() {
7490 let first = ObjectId::from_hex(
7491 ObjectFormat::Sha1,
7492 "1111111111111111111111111111111111111111",
7493 )
7494 .expect("test operation should succeed");
7495 let second = ObjectId::from_hex(
7496 ObjectFormat::Sha1,
7497 "2222222222222222222222222222222222222222",
7498 )
7499 .expect("test operation should succeed");
7500 let input = b"1111111111111111111111111111111111111111\t\tbranch 'main' of ../bundle.bdl\n2222222222222222222222222222222222222222\tnot-for-merge\ttag 'v1' of ../bundle.bdl\n";
7501 let records =
7502 parse_fetch_head(ObjectFormat::Sha1, input).expect("test operation should succeed");
7503 assert_eq!(
7504 records,
7505 vec![
7506 FetchHeadRecord {
7507 oid: first,
7508 not_for_merge: false,
7509 description: "branch 'main' of ../bundle.bdl".into(),
7510 },
7511 FetchHeadRecord {
7512 oid: second,
7513 not_for_merge: true,
7514 description: "tag 'v1' of ../bundle.bdl".into(),
7515 },
7516 ]
7517 );
7518 assert_eq!(
7519 encode_fetch_head(&records).expect("test operation should succeed"),
7520 input
7521 );
7522 assert_eq!(
7523 parse_fetch_head(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
7524 Vec::<FetchHeadRecord>::new()
7525 );
7526 assert_eq!(
7527 fetch_head_remote_description("refs/heads/main", "../bundle.bdl")
7528 .expect("test operation should succeed"),
7529 "branch 'main' of ../bundle.bdl"
7530 );
7531 assert_eq!(
7532 fetch_head_remote_description("refs/tags/v1", "../bundle.bdl")
7533 .expect("test operation should succeed"),
7534 "tag 'v1' of ../bundle.bdl"
7535 );
7536 assert_eq!(
7537 fetch_head_remote_description("HEAD", "../bundle.bdl")
7538 .expect("test operation should succeed"),
7539 "HEAD of ../bundle.bdl"
7540 );
7541 }
7542
7543 #[test]
7544 fn fetch_head_records_streams_round_trip() {
7545 let records = vec![FetchHeadRecord {
7546 oid: ObjectId::from_hex(
7547 ObjectFormat::Sha1,
7548 "1111111111111111111111111111111111111111",
7549 )
7550 .expect("test operation should succeed"),
7551 not_for_merge: false,
7552 description: "branch 'main' of ../bundle.bdl".into(),
7553 }];
7554 let mut encoded = Vec::new();
7555 write_fetch_head(&mut encoded, &records).expect("test operation should succeed");
7556 let mut input = encoded.as_slice();
7557 assert_eq!(
7558 read_fetch_head(ObjectFormat::Sha1, &mut input).expect("test operation should succeed"),
7559 records
7560 );
7561 assert!(input.is_empty());
7562 }
7563
7564 #[test]
7565 fn fetch_head_records_reject_malformed_lines() {
7566 assert!(
7567 parse_fetch_head(
7568 ObjectFormat::Sha1,
7569 b"1111111111111111111111111111111111111111\t\tbranch 'main'"
7570 )
7571 .is_err()
7572 );
7573 assert!(
7574 parse_fetch_head(
7575 ObjectFormat::Sha1,
7576 b"1111111111111111111111111111111111111111\tfor-merge\tbranch 'main'\n"
7577 )
7578 .is_err()
7579 );
7580 assert!(parse_fetch_head(ObjectFormat::Sha1, b"not-a-hash\t\tbranch 'main'\n").is_err());
7581 assert!(
7582 encode_fetch_head(&[FetchHeadRecord {
7583 oid: ObjectId::from_hex(
7584 ObjectFormat::Sha1,
7585 "1111111111111111111111111111111111111111"
7586 )
7587 .expect("test operation should succeed"),
7588 not_for_merge: false,
7589 description: "bad\ndescription".into(),
7590 }])
7591 .is_err()
7592 );
7593 }
7594
7595 #[test]
7596 fn fetch_planner_maps_direct_pattern_and_negative_refspecs() {
7597 let main = ObjectId::from_hex(
7598 ObjectFormat::Sha1,
7599 "1111111111111111111111111111111111111111",
7600 )
7601 .expect("test operation should succeed");
7602 let next = ObjectId::from_hex(
7603 ObjectFormat::Sha1,
7604 "2222222222222222222222222222222222222222",
7605 )
7606 .expect("test operation should succeed");
7607 let refs = vec![
7608 RefAdvertisement {
7609 oid: main.clone(),
7610 name: "refs/heads/main".into(),
7611 capabilities: Vec::new(),
7612 },
7613 RefAdvertisement {
7614 oid: next.clone(),
7615 name: "refs/heads/tmp".into(),
7616 capabilities: Vec::new(),
7617 },
7618 ];
7619 let refspecs = vec![
7620 parse_refspec("+refs/heads/*:refs/remotes/origin/*")
7621 .expect("test operation should succeed"),
7622 parse_refspec("^refs/heads/tmp").expect("test operation should succeed"),
7623 ];
7624 assert_eq!(
7625 plan_fetch_ref_updates(&refs, &refspecs, false).expect("test operation should succeed"),
7626 vec![FetchRefUpdate {
7627 src: "refs/heads/main".into(),
7628 dst: Some("refs/remotes/origin/main".into()),
7629 oid: main,
7630 not_for_merge: false,
7631 }]
7632 );
7633 }
7634
7635 #[test]
7636 fn fetch_planner_autofollows_tags_and_builds_fetch_head_records() {
7637 let commit = ObjectId::from_hex(
7638 ObjectFormat::Sha1,
7639 "1111111111111111111111111111111111111111",
7640 )
7641 .expect("test operation should succeed");
7642 let refs = vec![
7643 RefAdvertisement {
7644 oid: commit.clone(),
7645 name: "refs/heads/main".into(),
7646 capabilities: Vec::new(),
7647 },
7648 RefAdvertisement {
7649 oid: commit.clone(),
7650 name: "refs/tags/v1".into(),
7651 capabilities: Vec::new(),
7652 },
7653 ];
7654 let refspecs = vec![
7655 parse_refspec("refs/heads/main:refs/heads/main")
7656 .expect("test operation should succeed"),
7657 ];
7658 let updates =
7659 plan_fetch_ref_updates(&refs, &refspecs, true).expect("test operation should succeed");
7660 assert_eq!(
7661 updates,
7662 vec![
7663 FetchRefUpdate {
7664 src: "refs/heads/main".into(),
7665 dst: Some("refs/heads/main".into()),
7666 oid: commit.clone(),
7667 not_for_merge: false,
7668 },
7669 FetchRefUpdate {
7670 src: "refs/tags/v1".into(),
7671 dst: Some("refs/tags/v1".into()),
7672 oid: commit.clone(),
7673 not_for_merge: true,
7674 },
7675 ]
7676 );
7677 assert_eq!(
7678 fetch_ref_updates_to_fetch_head(&updates, "../bundle.bdl")
7679 .expect("test operation should succeed"),
7680 vec![
7681 FetchHeadRecord {
7682 oid: commit.clone(),
7683 not_for_merge: false,
7684 description: "branch 'main' of ../bundle.bdl".into(),
7685 },
7686 FetchHeadRecord {
7687 oid: commit,
7688 not_for_merge: true,
7689 description: "tag 'v1' of ../bundle.bdl".into(),
7690 },
7691 ]
7692 );
7693 }
7694
7695 #[test]
7696 fn fetch_planner_rejects_missing_or_sourceless_refspecs() {
7697 let refs = vec![RefAdvertisement {
7698 oid: ObjectId::from_hex(
7699 ObjectFormat::Sha1,
7700 "1111111111111111111111111111111111111111",
7701 )
7702 .expect("test operation should succeed"),
7703 name: "refs/heads/main".into(),
7704 capabilities: Vec::new(),
7705 }];
7706 assert!(
7707 plan_fetch_ref_updates(
7708 &refs,
7709 &[parse_refspec("refs/heads/missing").expect("test operation should succeed")],
7710 false
7711 )
7712 .is_err()
7713 );
7714 assert!(
7715 plan_fetch_ref_updates(
7716 &refs,
7717 &[parse_refspec(":refs/heads/main").expect("test operation should succeed")],
7718 false
7719 )
7720 .is_err()
7721 );
7722 }
7723
7724 #[test]
7725 fn fetch_planner_sourceless_positive_refspec_returns_err_not_panic() {
7726 let refs = vec![RefAdvertisement {
7731 oid: ObjectId::from_hex(
7732 ObjectFormat::Sha1,
7733 "1111111111111111111111111111111111111111",
7734 )
7735 .expect("test operation should succeed"),
7736 name: "refs/heads/main".into(),
7737 capabilities: Vec::new(),
7738 }];
7739 let malformed = RefSpec {
7740 force: false,
7741 negative: false,
7742 src: None,
7743 dst: Some("refs/heads/main".into()),
7744 pattern: false,
7745 };
7746 let result = plan_fetch_ref_updates(&refs, &[malformed], false);
7747 assert!(
7748 result.is_err(),
7749 "sourceless positive refspec must yield Err, got {result:?}"
7750 );
7751 }
7752
7753 #[test]
7754 fn push_planner_builds_create_update_delete_and_matching_commands() {
7755 let old = ObjectId::from_hex(
7756 ObjectFormat::Sha1,
7757 "1111111111111111111111111111111111111111",
7758 )
7759 .expect("test operation should succeed");
7760 let new = ObjectId::from_hex(
7761 ObjectFormat::Sha1,
7762 "2222222222222222222222222222222222222222",
7763 )
7764 .expect("test operation should succeed");
7765 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
7766 let local_refs = vec![
7767 PushSourceRef {
7768 oid: new.clone(),
7769 name: "refs/heads/main".into(),
7770 },
7771 PushSourceRef {
7772 oid: new.clone(),
7773 name: "refs/heads/new".into(),
7774 },
7775 ];
7776 let remote_refs = vec![
7777 RefAdvertisement {
7778 oid: old.clone(),
7779 name: "refs/heads/main".into(),
7780 capabilities: Vec::new(),
7781 },
7782 RefAdvertisement {
7783 oid: old.clone(),
7784 name: "refs/heads/old".into(),
7785 capabilities: Vec::new(),
7786 },
7787 ];
7788
7789 assert_eq!(
7790 plan_push_commands(
7791 ObjectFormat::Sha1,
7792 &local_refs,
7793 &remote_refs,
7794 &[parse_refspec("refs/heads/main").expect("test operation should succeed")],
7795 )
7796 .expect("test operation should succeed"),
7797 vec![ReceivePackCommand {
7798 old_id: old.clone(),
7799 new_id: new.clone(),
7800 name: "refs/heads/main".into(),
7801 }]
7802 );
7803 assert_eq!(
7804 plan_push_commands(
7805 ObjectFormat::Sha1,
7806 &local_refs,
7807 &remote_refs,
7808 &[parse_refspec("refs/heads/new:refs/heads/new")
7809 .expect("test operation should succeed")],
7810 )
7811 .expect("test operation should succeed"),
7812 vec![ReceivePackCommand {
7813 old_id: zero.clone(),
7814 new_id: new.clone(),
7815 name: "refs/heads/new".into(),
7816 }]
7817 );
7818 assert_eq!(
7819 plan_push_commands(
7820 ObjectFormat::Sha1,
7821 &local_refs,
7822 &remote_refs,
7823 &[parse_refspec(":refs/heads/old").expect("test operation should succeed")],
7824 )
7825 .expect("test operation should succeed"),
7826 vec![ReceivePackCommand {
7827 old_id: old.clone(),
7828 new_id: zero,
7829 name: "refs/heads/old".into(),
7830 }]
7831 );
7832 assert_eq!(
7833 plan_push_commands(
7834 ObjectFormat::Sha1,
7835 &local_refs,
7836 &remote_refs,
7837 &[parse_refspec(":").expect("test operation should succeed")],
7838 )
7839 .expect("test operation should succeed"),
7840 vec![ReceivePackCommand {
7841 old_id: old,
7842 new_id: new,
7843 name: "refs/heads/main".into(),
7844 }]
7845 );
7846 }
7847
7848 #[test]
7849 fn push_planner_builds_wildcard_commands_and_rejects_bad_refspecs() {
7850 let new = ObjectId::from_hex(
7851 ObjectFormat::Sha1,
7852 "2222222222222222222222222222222222222222",
7853 )
7854 .expect("test operation should succeed");
7855 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
7856 let local_refs = vec![PushSourceRef {
7857 oid: new.clone(),
7858 name: "refs/heads/topic".into(),
7859 }];
7860 let commands = plan_push_commands(
7861 ObjectFormat::Sha1,
7862 &local_refs,
7863 &[],
7864 &[parse_refspec("refs/heads/*:refs/heads/review/*")
7865 .expect("test operation should succeed")],
7866 )
7867 .expect("test operation should succeed");
7868 assert_eq!(
7869 commands,
7870 vec![ReceivePackCommand {
7871 old_id: zero,
7872 new_id: new,
7873 name: "refs/heads/review/topic".into(),
7874 }]
7875 );
7876 assert!(
7877 plan_push_commands(
7878 ObjectFormat::Sha1,
7879 &local_refs,
7880 &[],
7881 &[parse_refspec("^refs/heads/topic").expect("test operation should succeed")],
7882 )
7883 .is_err()
7884 );
7885 assert!(
7886 plan_push_commands(
7887 ObjectFormat::Sha1,
7888 &local_refs,
7889 &[],
7890 &[parse_refspec(":refs/heads/missing").expect("test operation should succeed")],
7891 )
7892 .is_err()
7893 );
7894 }
7895
7896 #[test]
7897 fn receive_pack_push_request_builder_negotiates_capabilities() {
7898 let old_id = ObjectId::from_hex(
7899 ObjectFormat::Sha1,
7900 "1111111111111111111111111111111111111111",
7901 )
7902 .expect("test operation should succeed");
7903 let new_id = ObjectId::from_hex(
7904 ObjectFormat::Sha1,
7905 "2222222222222222222222222222222222222222",
7906 )
7907 .expect("test operation should succeed");
7908 let features = ReceivePackFeatures {
7909 report_status_v2: true,
7910 atomic: true,
7911 ofs_delta: true,
7912 push_options: true,
7913 side_band_64k: true,
7914 quiet: true,
7915 object_format: Some(ObjectFormat::Sha1),
7916 ..ReceivePackFeatures::default()
7917 };
7918 let request = build_receive_pack_push_request(
7919 &features,
7920 vec![ReceivePackCommand {
7921 old_id,
7922 new_id,
7923 name: "refs/heads/main".into(),
7924 }],
7925 b"PACKdata".to_vec(),
7926 ReceivePackPushRequestOptions {
7927 report_status_v2: true,
7928 atomic: true,
7929 ofs_delta: true,
7930 side_band_64k: true,
7931 quiet: true,
7932 agent: Some("sley/0".into()),
7933 object_format: Some(ObjectFormat::Sha1),
7934 push_options: vec!["ci.skip".into()],
7935 ..ReceivePackPushRequestOptions::default()
7936 },
7937 )
7938 .expect("test operation should succeed");
7939 assert_eq!(
7940 request.commands.capabilities,
7941 vec![
7942 Capability {
7943 name: "report-status-v2".into(),
7944 value: None,
7945 },
7946 Capability {
7947 name: "atomic".into(),
7948 value: None,
7949 },
7950 Capability {
7951 name: "ofs-delta".into(),
7952 value: None,
7953 },
7954 Capability {
7955 name: "side-band-64k".into(),
7956 value: None,
7957 },
7958 Capability {
7959 name: "quiet".into(),
7960 value: None,
7961 },
7962 Capability {
7963 name: "agent".into(),
7964 value: Some("sley/0".into()),
7965 },
7966 Capability {
7967 name: "object-format".into(),
7968 value: Some("sha1".into()),
7969 },
7970 Capability {
7971 name: "push-options".into(),
7972 value: None,
7973 },
7974 ]
7975 );
7976 assert_eq!(request.push_options, Some(vec!["ci.skip".into()]));
7977 validate_receive_pack_push_request_features(&features, &request)
7978 .expect("test operation should succeed");
7979 }
7980
7981 #[test]
7982 fn receive_pack_push_request_builder_handles_delete_only_and_rejects_unadvertised() {
7983 let old_id = ObjectId::from_hex(
7984 ObjectFormat::Sha1,
7985 "1111111111111111111111111111111111111111",
7986 )
7987 .expect("test operation should succeed");
7988 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
7989 let features = ReceivePackFeatures {
7990 delete_refs: true,
7991 ..ReceivePackFeatures::default()
7992 };
7993 let request = build_receive_pack_push_request(
7994 &features,
7995 vec![ReceivePackCommand {
7996 old_id,
7997 new_id: zero,
7998 name: "refs/heads/old".into(),
7999 }],
8000 Vec::new(),
8001 ReceivePackPushRequestOptions::default(),
8002 )
8003 .expect("test operation should succeed");
8004 assert_eq!(
8005 request.commands.capabilities,
8006 vec![Capability {
8007 name: "delete-refs".into(),
8008 value: None,
8009 }]
8010 );
8011 assert!(request.packfile.is_empty());
8012
8013 assert!(
8014 build_receive_pack_push_request(
8015 &ReceivePackFeatures::default(),
8016 request.commands.commands.clone(),
8017 Vec::new(),
8018 ReceivePackPushRequestOptions::default(),
8019 )
8020 .is_err()
8021 );
8022 assert!(
8023 build_receive_pack_push_request(
8024 &features,
8025 request.commands.commands,
8026 b"PACK".to_vec(),
8027 ReceivePackPushRequestOptions::default(),
8028 )
8029 .is_err()
8030 );
8031 assert!(
8032 build_receive_pack_push_request(
8033 &features,
8034 Vec::new(),
8035 Vec::new(),
8036 ReceivePackPushRequestOptions {
8037 push_options: vec!["ci.skip".into()],
8038 ..ReceivePackPushRequestOptions::default()
8039 },
8040 )
8041 .is_err()
8042 );
8043 }
8044
8045 #[test]
8046 fn smart_http_helpers_build_paths_and_content_types() {
8047 let sha1 = ObjectId::from_hex(
8048 ObjectFormat::Sha1,
8049 "1111111111111111111111111111111111111111",
8050 )
8051 .expect("test operation should succeed");
8052 let sha256 = ObjectId::from_hex(
8053 ObjectFormat::Sha256,
8054 "2222222222222222222222222222222222222222222222222222222222222222",
8055 )
8056 .expect("test operation should succeed");
8057 assert_eq!(
8058 smart_http_info_refs_path("/repo.git/", GitService::UploadPack)
8059 .expect("test operation should succeed"),
8060 "/repo.git/info/refs?service=git-upload-pack"
8061 );
8062 assert_eq!(
8063 dumb_http_info_refs_path("/repo.git/").expect("test operation should succeed"),
8064 "/repo.git/info/refs"
8065 );
8066 assert_eq!(
8067 dumb_http_alternates_path("/repo.git").expect("test operation should succeed"),
8068 "/repo.git/objects/info/http-alternates"
8069 );
8070 assert_eq!(
8071 dumb_http_packs_path("/repo.git/").expect("test operation should succeed"),
8072 "/repo.git/objects/info/packs"
8073 );
8074 assert_eq!(
8075 dumb_http_loose_object_path("/repo.git/", &sha1)
8076 .expect("test operation should succeed"),
8077 "/repo.git/objects/11/11111111111111111111111111111111111111"
8078 );
8079 assert_eq!(
8080 dumb_http_loose_object_path("/repo.git/", &sha256)
8081 .expect("test operation should succeed"),
8082 "/repo.git/objects/22/22222222222222222222222222222222222222222222222222222222222222"
8083 );
8084 assert_eq!(
8085 dumb_http_pack_file_path("/repo.git/", &sha1).expect("test operation should succeed"),
8086 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.pack"
8087 );
8088 assert_eq!(
8089 dumb_http_pack_index_path("/repo.git/", &sha1).expect("test operation should succeed"),
8090 "/repo.git/objects/pack/pack-1111111111111111111111111111111111111111.idx"
8091 );
8092 assert_eq!(
8093 smart_http_rpc_path("/repo.git", GitService::ReceivePack)
8094 .expect("test operation should succeed"),
8095 "/repo.git/git-receive-pack"
8096 );
8097 assert_eq!(
8098 smart_http_advertisement_content_type(GitService::UploadPack)
8099 .expect("test operation should succeed"),
8100 "application/x-git-upload-pack-advertisement"
8101 );
8102 assert_eq!(
8103 smart_http_rpc_request_content_type(GitService::UploadPack)
8104 .expect("test operation should succeed"),
8105 "application/x-git-upload-pack-request"
8106 );
8107 assert_eq!(
8108 smart_http_rpc_result_content_type(GitService::ReceivePack)
8109 .expect("test operation should succeed"),
8110 "application/x-git-receive-pack-result"
8111 );
8112 assert_eq!(
8113 parse_smart_http_advertisement_content_type(
8114 "Application/X-Git-Upload-Pack-Advertisement"
8115 )
8116 .expect("test operation should succeed"),
8117 GitService::UploadPack
8118 );
8119 assert_eq!(
8120 parse_smart_http_rpc_request_content_type("application/x-git-receive-pack-request")
8121 .expect("test operation should succeed"),
8122 GitService::ReceivePack
8123 );
8124 assert_eq!(
8125 parse_smart_http_rpc_result_content_type("application/x-git-upload-pack-result")
8126 .expect("test operation should succeed"),
8127 GitService::UploadPack
8128 );
8129 }
8130
8131 #[test]
8132 fn smart_http_helpers_reject_invalid_services_paths_and_content_types() {
8133 let oid = ObjectId::from_hex(
8134 ObjectFormat::Sha1,
8135 "1111111111111111111111111111111111111111",
8136 )
8137 .expect("test operation should succeed");
8138 assert!(smart_http_info_refs_path("repo.git", GitService::UploadPack).is_err());
8139 assert!(smart_http_rpc_path("/repo.git?x=1", GitService::UploadPack).is_err());
8140 assert!(dumb_http_info_refs_path("repo.git").is_err());
8141 assert!(dumb_http_alternates_path("/repo.git#fragment").is_err());
8142 assert!(dumb_http_packs_path("/repo.git?query").is_err());
8143 assert!(dumb_http_loose_object_path("repo.git", &oid).is_err());
8144 assert!(dumb_http_pack_file_path("/repo.git#fragment", &oid).is_err());
8145 assert!(dumb_http_pack_index_path("/repo.git?query", &oid).is_err());
8146 assert!(smart_http_info_refs_path("/repo.git", GitService::UploadArchive).is_err());
8147 assert!(smart_http_advertisement_content_type(GitService::UploadArchive).is_err());
8148 assert!(
8149 parse_smart_http_advertisement_content_type(
8150 "application/x-git-upload-archive-advertisement"
8151 )
8152 .is_err()
8153 );
8154 assert!(
8155 parse_smart_http_rpc_request_content_type("application/x-git-upload-pack-result")
8156 .is_err()
8157 );
8158 assert!(
8159 parse_smart_http_rpc_result_content_type(
8160 "application/x-git-receive-pack-result; charset=utf-8"
8161 )
8162 .is_err()
8163 );
8164 }
8165
8166 #[test]
8167 fn sideband_packets_parse_and_encode_channels() {
8168 let payloads = vec![
8169 b"\x01PACK bytes".to_vec(),
8170 b"\x02counting objects\n".to_vec(),
8171 b"\x03fatal error\n".to_vec(),
8172 ];
8173 let packets = parse_sideband_packets(&payloads).expect("test operation should succeed");
8174 assert_eq!(
8175 packets,
8176 vec![
8177 SideBandPacket {
8178 channel: SideBandChannel::Data,
8179 data: b"PACK bytes".to_vec(),
8180 },
8181 SideBandPacket {
8182 channel: SideBandChannel::Progress,
8183 data: b"counting objects\n".to_vec(),
8184 },
8185 SideBandPacket {
8186 channel: SideBandChannel::Fatal,
8187 data: b"fatal error\n".to_vec(),
8188 },
8189 ]
8190 );
8191 assert_eq!(
8192 encode_sideband_packets(&packets).expect("test operation should succeed"),
8193 payloads
8194 );
8195 }
8196
8197 #[test]
8198 fn sideband_stream_parses_encodes_and_demuxes_packets() {
8199 let frames = vec![
8200 PktLineFrame::Data(vec![1, b'P', b'A']),
8201 PktLineFrame::Data(vec![2, b'c', b'o', b'u', b'n', b't', b'\n']),
8202 PktLineFrame::Data(vec![1, b'C', b'K']),
8203 PktLineFrame::Flush,
8204 ];
8205 let packets = parse_sideband_stream(&frames).expect("test operation should succeed");
8206 assert_eq!(
8207 packets,
8208 vec![
8209 SideBandPacket {
8210 channel: SideBandChannel::Data,
8211 data: b"PA".to_vec(),
8212 },
8213 SideBandPacket {
8214 channel: SideBandChannel::Progress,
8215 data: b"count\n".to_vec(),
8216 },
8217 SideBandPacket {
8218 channel: SideBandChannel::Data,
8219 data: b"CK".to_vec(),
8220 },
8221 ]
8222 );
8223 assert_eq!(
8224 encode_sideband_stream(&packets).expect("test operation should succeed"),
8225 frames
8226 );
8227 assert_eq!(
8228 demux_sideband_stream(&frames).expect("test operation should succeed"),
8229 SideBandDemux {
8230 data: b"PACK".to_vec(),
8231 progress: vec![b"count\n".to_vec()],
8232 }
8233 );
8234 }
8235
8236 #[test]
8237 fn sideband_stream_reads_and_writes_until_flush() {
8238 let packets = vec![
8239 SideBandPacket {
8240 channel: SideBandChannel::Data,
8241 data: b"PACK".to_vec(),
8242 },
8243 SideBandPacket {
8244 channel: SideBandChannel::Progress,
8245 data: b"done\n".to_vec(),
8246 },
8247 ];
8248 let mut encoded = Vec::new();
8249 write_sideband_stream(&mut encoded, &packets).expect("test operation should succeed");
8250 encoded.extend_from_slice(b"tail");
8251
8252 let mut input = encoded.as_slice();
8253 assert_eq!(
8254 read_sideband_stream(&mut input).expect("test operation should succeed"),
8255 packets
8256 );
8257 assert_eq!(input, b"tail");
8258
8259 let mut input = encoded.as_slice();
8260 assert_eq!(
8261 read_and_demux_sideband_stream(&mut input).expect("test operation should succeed"),
8262 SideBandDemux {
8263 data: b"PACK".to_vec(),
8264 progress: vec![b"done\n".to_vec()],
8265 }
8266 );
8267 assert_eq!(input, b"tail");
8268 }
8269
8270 #[test]
8271 fn sideband_packets_demux_data_and_progress() {
8272 let payloads = vec![
8273 b"\x01PACK".to_vec(),
8274 b"\x02counting objects\n".to_vec(),
8275 b"\x01 bytes".to_vec(),
8276 b"\x02done\n".to_vec(),
8277 ];
8278 assert_eq!(
8279 parse_and_demux_sideband_packets(&payloads).expect("test operation should succeed"),
8280 SideBandDemux {
8281 data: b"PACK bytes".to_vec(),
8282 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
8283 }
8284 );
8285 }
8286
8287 #[test]
8288 fn sideband_packets_reject_bad_channels_and_oversize_payloads() {
8289 assert!(parse_sideband_packet(b"").is_err());
8290 assert!(parse_sideband_packet(b"\x04bad").is_err());
8291 assert!(
8292 parse_sideband_stream(&[PktLineFrame::Data(vec![1, b'P', b'A', b'C', b'K'])]).is_err()
8293 );
8294 assert!(parse_sideband_stream(&[PktLineFrame::Delimiter, PktLineFrame::Flush]).is_err());
8295 assert!(
8296 parse_sideband_stream(&[
8297 PktLineFrame::Data(vec![1, b'P', b'A']),
8298 PktLineFrame::Flush,
8299 PktLineFrame::Data(vec![1, b'C', b'K']),
8300 ])
8301 .is_err()
8302 );
8303 assert!(
8304 parse_sideband_stream(&[
8305 PktLineFrame::Data(vec![1, b'P', b'A']),
8306 PktLineFrame::Data(b"\x04bad".to_vec()),
8307 PktLineFrame::Flush,
8308 ])
8309 .is_err()
8310 );
8311 assert!(
8312 encode_sideband_packet(&SideBandPacket {
8313 channel: SideBandChannel::Data,
8314 data: vec![0; PKT_LINE_MAX_PAYLOAD_LEN],
8315 })
8316 .is_err()
8317 );
8318 assert!(
8319 demux_sideband_packets(&[SideBandPacket {
8320 channel: SideBandChannel::Fatal,
8321 data: b"remote died\n".to_vec(),
8322 }])
8323 .is_err()
8324 );
8325 }
8326
8327 #[test]
8328 fn upload_archive_request_parses_and_encodes_arguments() {
8329 let frames = vec![
8330 PktLineFrame::Data(b"argument --format=tar\n".to_vec()),
8331 PktLineFrame::Data(b"argument HEAD:dir with spaces\n".to_vec()),
8332 PktLineFrame::Flush,
8333 ];
8334 let request = parse_upload_archive_request(&frames).expect("test operation should succeed");
8335 assert_eq!(
8336 request,
8337 UploadArchiveRequest {
8338 arguments: vec!["--format=tar".into(), "HEAD:dir with spaces".into()],
8339 }
8340 );
8341 assert_eq!(
8342 encode_upload_archive_request(&request).expect("test operation should succeed"),
8343 frames
8344 );
8345 }
8346
8347 #[test]
8348 fn upload_archive_request_streams_round_trip() {
8349 let request = UploadArchiveRequest {
8350 arguments: vec!["--prefix=src/".into(), "main".into()],
8351 };
8352 let mut encoded = Vec::new();
8353 write_upload_archive_request(&mut encoded, &request)
8354 .expect("test operation should succeed");
8355 encoded.extend_from_slice(b"tail");
8356
8357 let mut input = encoded.as_slice();
8358 assert_eq!(
8359 read_upload_archive_request(&mut input).expect("test operation should succeed"),
8360 request
8361 );
8362 assert_eq!(input, b"tail");
8363 }
8364
8365 #[test]
8366 fn upload_archive_request_rejects_malformed_streams() {
8367 assert!(parse_upload_archive_request(&[PktLineFrame::Flush]).is_err());
8368 assert!(
8369 parse_upload_archive_request(&[
8370 PktLineFrame::Data(b"--format=tar\n".to_vec()),
8371 PktLineFrame::Flush,
8372 ])
8373 .is_err()
8374 );
8375 assert!(
8376 parse_upload_archive_request(&[
8377 PktLineFrame::Data(b"argument HEAD\n".to_vec()),
8378 PktLineFrame::Delimiter,
8379 PktLineFrame::Flush,
8380 ])
8381 .is_err()
8382 );
8383 assert!(
8384 encode_upload_archive_request(&UploadArchiveRequest {
8385 arguments: vec!["bad\narg".into()],
8386 })
8387 .is_err()
8388 );
8389 }
8390
8391 #[test]
8392 fn upload_archive_response_parses_ack_sideband_and_nack() {
8393 let ack_frames = vec![
8394 PktLineFrame::Data(b"ACK\n".to_vec()),
8395 PktLineFrame::Data(b"\x01tar bytes".to_vec()),
8396 PktLineFrame::Data(b"\x02progress\n".to_vec()),
8397 PktLineFrame::Flush,
8398 ];
8399 let response =
8400 parse_upload_archive_response(&ack_frames).expect("test operation should succeed");
8401 assert_eq!(
8402 response,
8403 UploadArchiveResponse::Ack {
8404 sideband: vec![
8405 SideBandPacket {
8406 channel: SideBandChannel::Data,
8407 data: b"tar bytes".to_vec(),
8408 },
8409 SideBandPacket {
8410 channel: SideBandChannel::Progress,
8411 data: b"progress\n".to_vec(),
8412 },
8413 ],
8414 }
8415 );
8416 assert_eq!(
8417 encode_upload_archive_response(&response).expect("test operation should succeed"),
8418 ack_frames
8419 );
8420 assert_eq!(
8421 demux_upload_archive_response(&response).expect("test operation should succeed"),
8422 SideBandDemux {
8423 data: b"tar bytes".to_vec(),
8424 progress: vec![b"progress\n".to_vec()],
8425 }
8426 );
8427
8428 let nack = UploadArchiveResponse::Nack {
8429 message: "unreachable tree".into(),
8430 };
8431 let nack_frames = vec![
8432 PktLineFrame::Data(b"NACK unreachable tree\n".to_vec()),
8433 PktLineFrame::Flush,
8434 ];
8435 assert_eq!(
8436 parse_upload_archive_response(&nack_frames).expect("test operation should succeed"),
8437 nack
8438 );
8439 assert_eq!(
8440 encode_upload_archive_response(&nack).expect("test operation should succeed"),
8441 nack_frames
8442 );
8443 assert!(demux_upload_archive_response(&nack).is_err());
8444 }
8445
8446 #[test]
8447 fn upload_archive_response_streams_round_trip() {
8448 let response = UploadArchiveResponse::Ack {
8449 sideband: vec![SideBandPacket {
8450 channel: SideBandChannel::Data,
8451 data: b"tar bytes".to_vec(),
8452 }],
8453 };
8454 let mut encoded = Vec::new();
8455 write_upload_archive_response(&mut encoded, &response)
8456 .expect("test operation should succeed");
8457 encoded.extend_from_slice(b"tail");
8458
8459 let mut input = encoded.as_slice();
8460 assert_eq!(
8461 read_upload_archive_response(&mut input).expect("test operation should succeed"),
8462 response
8463 );
8464 assert_eq!(input, b"tail");
8465 }
8466
8467 #[test]
8468 fn upload_archive_response_rejects_malformed_streams() {
8469 assert!(parse_upload_archive_response(&[]).is_err());
8470 assert!(
8471 parse_upload_archive_response(&[
8472 PktLineFrame::Data(b"ACK\n".to_vec()),
8473 PktLineFrame::Flush,
8474 PktLineFrame::Data(b"\x01tail".to_vec()),
8475 ])
8476 .is_err()
8477 );
8478 assert!(
8479 parse_upload_archive_response(&[
8480 PktLineFrame::Data(b"NACK\n".to_vec()),
8481 PktLineFrame::Flush,
8482 ])
8483 .is_err()
8484 );
8485 assert!(
8486 parse_upload_archive_response(&[
8487 PktLineFrame::Data(b"NACK denied\n".to_vec()),
8488 PktLineFrame::Data(b"\x02extra\n".to_vec()),
8489 PktLineFrame::Flush,
8490 ])
8491 .is_err()
8492 );
8493 assert!(
8494 encode_upload_archive_response(&UploadArchiveResponse::Nack {
8495 message: "bad\nmessage".into(),
8496 })
8497 .is_err()
8498 );
8499 }
8500
8501 #[test]
8502 fn capabilities_parse_and_encode_tokens() {
8503 let capabilities = parse_capabilities(
8504 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main\n",
8505 )
8506 .expect("test operation should succeed");
8507 assert_eq!(
8508 capabilities,
8509 vec![
8510 Capability {
8511 name: "multi_ack".into(),
8512 value: None,
8513 },
8514 Capability {
8515 name: "thin-pack".into(),
8516 value: None,
8517 },
8518 Capability {
8519 name: "agent".into(),
8520 value: Some("git/2.54.0".into()),
8521 },
8522 Capability {
8523 name: "symref".into(),
8524 value: Some("HEAD:refs/heads/main".into()),
8525 },
8526 ]
8527 );
8528 assert_eq!(
8529 encode_capabilities(&capabilities).expect("test operation should succeed"),
8530 b"multi_ack thin-pack agent=git/2.54.0 symref=HEAD:refs/heads/main"
8531 );
8532 }
8533
8534 #[test]
8535 fn capabilities_reject_empty_or_delimited_fields() {
8536 assert!(parse_capabilities(b"multi_ack thin-pack").is_err());
8537 assert!(parse_capabilities(b"agent=").is_err());
8538 assert!(parse_capabilities(b"symref=HEAD:refs/heads/main\nbad").is_err());
8539 assert!(
8540 encode_capabilities(&[Capability {
8541 name: "bad name".into(),
8542 value: None,
8543 }])
8544 .is_err()
8545 );
8546 }
8547
8548 #[test]
8549 fn protocol_v2_object_format_uses_capability_or_defaults_to_sha1() {
8550 assert_eq!(
8551 protocol_v2_object_format(&[]).expect("test operation should succeed"),
8552 ObjectFormat::Sha1
8553 );
8554 assert_eq!(
8555 protocol_v2_object_format(&[Capability {
8556 name: "object-format".into(),
8557 value: Some("sha256".into()),
8558 }])
8559 .expect("test operation should succeed"),
8560 ObjectFormat::Sha256
8561 );
8562 assert!(
8563 protocol_v2_object_format(&[Capability {
8564 name: "object-format".into(),
8565 value: None,
8566 }])
8567 .is_err()
8568 );
8569 assert!(
8570 protocol_v2_object_format(&[
8571 Capability {
8572 name: "object-format".into(),
8573 value: Some("sha1".into()),
8574 },
8575 Capability {
8576 name: "object-format".into(),
8577 value: Some("sha256".into()),
8578 },
8579 ])
8580 .is_err()
8581 );
8582 assert!(
8583 protocol_v2_object_format(&[Capability {
8584 name: "object-format".into(),
8585 value: Some("unknown".into()),
8586 }])
8587 .is_err()
8588 );
8589 }
8590
8591 #[test]
8592 fn protocol_v2_command_request_capabilities_validate_against_handshake() {
8593 let handshake = TransportHandshake {
8594 protocol: ProtocolVersion::V2,
8595 capabilities: vec![
8596 Capability {
8597 name: "fetch".into(),
8598 value: Some("shallow filter".into()),
8599 },
8600 Capability {
8601 name: "agent".into(),
8602 value: Some("sley/0".into()),
8603 },
8604 Capability {
8605 name: "object-format".into(),
8606 value: Some("sha1".into()),
8607 },
8608 ],
8609 };
8610 validate_protocol_v2_command_request_capabilities(
8611 &handshake,
8612 &ProtocolV2CommandRequest {
8613 command: "fetch".into(),
8614 capabilities: vec![
8615 Capability {
8616 name: "agent".into(),
8617 value: Some("client/1".into()),
8618 },
8619 Capability {
8620 name: "object-format".into(),
8621 value: Some("sha1".into()),
8622 },
8623 ],
8624 arguments: Vec::new(),
8625 },
8626 )
8627 .expect("test operation should succeed");
8628 assert!(
8629 validate_protocol_v2_command_request_capabilities(
8630 &handshake,
8631 &ProtocolV2CommandRequest {
8632 command: "ls-refs".into(),
8633 capabilities: Vec::new(),
8634 arguments: Vec::new(),
8635 },
8636 )
8637 .is_err()
8638 );
8639 assert!(
8640 validate_protocol_v2_command_request_capabilities(
8641 &handshake,
8642 &ProtocolV2CommandRequest {
8643 command: "fetch".into(),
8644 capabilities: vec![Capability {
8645 name: "server-option".into(),
8646 value: None,
8647 }],
8648 arguments: Vec::new(),
8649 },
8650 )
8651 .is_err()
8652 );
8653 assert!(
8654 validate_protocol_v2_command_request_capabilities(
8655 &handshake,
8656 &ProtocolV2CommandRequest {
8657 command: "fetch".into(),
8658 capabilities: vec![Capability {
8659 name: "object-format".into(),
8660 value: Some("sha256".into()),
8661 }],
8662 arguments: Vec::new(),
8663 },
8664 )
8665 .is_err()
8666 );
8667 assert!(
8668 validate_protocol_v2_command_request_capabilities(
8669 &handshake,
8670 &ProtocolV2CommandRequest {
8671 command: "fetch".into(),
8672 capabilities: vec![Capability {
8673 name: "agent".into(),
8674 value: None,
8675 }],
8676 arguments: Vec::new(),
8677 },
8678 )
8679 .is_err()
8680 );
8681 }
8682
8683 #[test]
8684 fn protocol_v2_command_options_parse_and_encode_known_capabilities() {
8685 let capabilities = vec![
8686 Capability {
8687 name: "agent".into(),
8688 value: Some("sley/0".into()),
8689 },
8690 Capability {
8691 name: "object-format".into(),
8692 value: Some("sha256".into()),
8693 },
8694 Capability {
8695 name: "server-option".into(),
8696 value: Some("trace=true".into()),
8697 },
8698 Capability {
8699 name: "server-option".into(),
8700 value: Some("region=west".into()),
8701 },
8702 Capability {
8703 name: "session-id".into(),
8704 value: Some("abc123".into()),
8705 },
8706 ];
8707 let options = parse_protocol_v2_command_options(&capabilities)
8708 .expect("test operation should succeed");
8709 assert_eq!(
8710 options,
8711 ProtocolV2CommandOptions {
8712 agent: Some("sley/0".into()),
8713 object_format: Some(ObjectFormat::Sha256),
8714 server_options: vec!["trace=true".into(), "region=west".into()],
8715 extra: vec![Capability {
8716 name: "session-id".into(),
8717 value: Some("abc123".into()),
8718 }],
8719 }
8720 );
8721 assert_eq!(
8722 encode_protocol_v2_command_options(&options).expect("test operation should succeed"),
8723 capabilities
8724 );
8725 }
8726
8727 #[test]
8728 fn protocol_v2_command_options_reject_malformed_known_capabilities() {
8729 assert!(
8730 parse_protocol_v2_command_options(&[
8731 Capability {
8732 name: "agent".into(),
8733 value: Some("sley/0".into()),
8734 },
8735 Capability {
8736 name: "agent".into(),
8737 value: Some("sley/1".into()),
8738 },
8739 ])
8740 .is_err()
8741 );
8742 assert!(
8743 parse_protocol_v2_command_options(&[Capability {
8744 name: "object-format".into(),
8745 value: Some("sha512".into()),
8746 }])
8747 .is_err()
8748 );
8749 assert!(
8750 parse_protocol_v2_command_options(&[Capability {
8751 name: "server-option".into(),
8752 value: None,
8753 }])
8754 .is_err()
8755 );
8756 assert!(
8757 encode_protocol_v2_command_options(&ProtocolV2CommandOptions {
8758 extra: vec![Capability {
8759 name: "server-option".into(),
8760 value: Some("trace=true".into()),
8761 }],
8762 ..ProtocolV2CommandOptions::default()
8763 })
8764 .is_err()
8765 );
8766 }
8767
8768 #[test]
8769 fn protocol_v2_ls_refs_features_parse_and_encode_advertisement() {
8770 let capabilities = vec![Capability {
8771 name: "ls-refs".into(),
8772 value: Some("unborn custom".into()),
8773 }];
8774 let features = parse_protocol_v2_ls_refs_features(&capabilities)
8775 .expect("test operation should succeed")
8776 .expect("test operation should succeed");
8777 assert_eq!(
8778 features,
8779 ProtocolV2LsRefsFeatures {
8780 unborn: true,
8781 unknown: vec!["custom".into()],
8782 }
8783 );
8784 assert_eq!(
8785 encode_protocol_v2_ls_refs_capability(&features)
8786 .expect("test operation should succeed"),
8787 capabilities[0]
8788 );
8789 assert_eq!(
8790 parse_protocol_v2_ls_refs_features(&[Capability {
8791 name: "ls-refs".into(),
8792 value: None,
8793 }])
8794 .expect("test operation should succeed")
8795 .expect("test operation should succeed"),
8796 ProtocolV2LsRefsFeatures::default()
8797 );
8798 assert!(
8799 parse_protocol_v2_ls_refs_features(&[Capability {
8800 name: "fetch".into(),
8801 value: Some("filter".into()),
8802 }])
8803 .expect("test operation should succeed")
8804 .is_none()
8805 );
8806 }
8807
8808 #[test]
8809 fn protocol_v2_ls_refs_features_reject_malformed_advertisements() {
8810 assert!(
8811 parse_protocol_v2_ls_refs_features(&[
8812 Capability {
8813 name: "ls-refs".into(),
8814 value: None,
8815 },
8816 Capability {
8817 name: "ls-refs".into(),
8818 value: None,
8819 },
8820 ])
8821 .is_err()
8822 );
8823 assert!(
8824 parse_protocol_v2_ls_refs_features(&[Capability {
8825 name: "ls-refs".into(),
8826 value: Some("unborn custom".into()),
8827 }])
8828 .is_err()
8829 );
8830 assert!(
8831 encode_protocol_v2_ls_refs_capability(&ProtocolV2LsRefsFeatures {
8832 unknown: vec!["unborn".into()],
8833 ..ProtocolV2LsRefsFeatures::default()
8834 })
8835 .is_err()
8836 );
8837 }
8838
8839 #[test]
8840 fn protocol_v2_ls_refs_command_request_validates_unborn_feature() {
8841 let handshake = TransportHandshake {
8842 protocol: ProtocolVersion::V2,
8843 capabilities: vec![Capability {
8844 name: "ls-refs".into(),
8845 value: Some("unborn".into()),
8846 }],
8847 };
8848 let request = ProtocolV2CommandRequest {
8849 command: "ls-refs".into(),
8850 capabilities: Vec::new(),
8851 arguments: vec![b"unborn".to_vec(), b"ref-prefix HEAD".to_vec()],
8852 };
8853 let parsed = validate_protocol_v2_ls_refs_command_request(&handshake, &request)
8854 .expect("test operation should succeed");
8855 assert!(parsed.unborn);
8856 assert_eq!(parsed.ref_prefixes, vec!["HEAD"]);
8857
8858 let blocked = TransportHandshake {
8859 protocol: ProtocolVersion::V2,
8860 capabilities: vec![Capability {
8861 name: "ls-refs".into(),
8862 value: None,
8863 }],
8864 };
8865 assert!(validate_protocol_v2_ls_refs_command_request(&blocked, &request).is_err());
8866 }
8867
8868 #[test]
8869 fn protocol_v2_fetch_features_parse_and_encode_advertisement() {
8870 let capabilities = vec![Capability {
8871 name: "fetch".into(),
8872 value: Some(
8873 "shallow wait-for-done filter ref-in-want sideband-all packfile-uris custom".into(),
8874 ),
8875 }];
8876 let features = parse_protocol_v2_fetch_features(&capabilities)
8877 .expect("test operation should succeed")
8878 .expect("test operation should succeed");
8879 assert_eq!(
8880 features,
8881 ProtocolV2FetchFeatures {
8882 shallow: true,
8883 wait_for_done: true,
8884 filter: true,
8885 ref_in_want: true,
8886 sideband_all: true,
8887 packfile_uris: true,
8888 unknown: vec!["custom".into()],
8889 }
8890 );
8891 assert_eq!(
8892 encode_protocol_v2_fetch_capability(&features).expect("test operation should succeed"),
8893 capabilities[0]
8894 );
8895 assert_eq!(
8896 parse_protocol_v2_fetch_features(&[Capability {
8897 name: "fetch".into(),
8898 value: None,
8899 }])
8900 .expect("test operation should succeed")
8901 .expect("test operation should succeed"),
8902 ProtocolV2FetchFeatures::default()
8903 );
8904 assert!(
8905 parse_protocol_v2_fetch_features(&[])
8906 .expect("test operation should succeed")
8907 .is_none()
8908 );
8909 }
8910
8911 #[test]
8912 fn protocol_v2_fetch_features_reject_malformed_advertisements() {
8913 assert!(
8914 parse_protocol_v2_fetch_features(&[
8915 Capability {
8916 name: "fetch".into(),
8917 value: None,
8918 },
8919 Capability {
8920 name: "fetch".into(),
8921 value: None,
8922 },
8923 ])
8924 .is_err()
8925 );
8926 assert!(
8927 parse_protocol_v2_fetch_features(&[Capability {
8928 name: "fetch".into(),
8929 value: Some("filter shallow".into()),
8930 }])
8931 .is_err()
8932 );
8933 assert!(
8934 encode_protocol_v2_fetch_capability(&ProtocolV2FetchFeatures {
8935 unknown: vec!["filter".into()],
8936 ..ProtocolV2FetchFeatures::default()
8937 })
8938 .is_err()
8939 );
8940 }
8941
8942 #[test]
8943 fn protocol_v2_fetch_request_features_validate_feature_gated_arguments() {
8944 let features = ProtocolV2FetchFeatures {
8945 shallow: true,
8946 wait_for_done: true,
8947 filter: true,
8948 ref_in_want: true,
8949 sideband_all: true,
8950 packfile_uris: true,
8951 unknown: Vec::new(),
8952 };
8953 validate_protocol_v2_fetch_request_features(
8954 &features,
8955 &ProtocolV2FetchRequest {
8956 want_refs: vec!["refs/heads/main".into()],
8957 shallow: vec![
8958 ObjectId::from_hex(
8959 ObjectFormat::Sha1,
8960 "1111111111111111111111111111111111111111",
8961 )
8962 .expect("test operation should succeed"),
8963 ],
8964 deepen: Some(1),
8965 filter: Some("blob:none".into()),
8966 packfile_uris: Some("https".into()),
8967 sideband_all: true,
8968 wait_for_done: true,
8969 ..ProtocolV2FetchRequest::default()
8970 },
8971 )
8972 .expect("test operation should succeed");
8973
8974 let request = ProtocolV2FetchRequest {
8975 want_refs: vec!["refs/heads/main".into()],
8976 filter: Some("blob:none".into()),
8977 sideband_all: true,
8978 ..ProtocolV2FetchRequest::default()
8979 };
8980 assert!(
8981 validate_protocol_v2_fetch_request_features(
8982 &ProtocolV2FetchFeatures::default(),
8983 &request,
8984 )
8985 .is_err()
8986 );
8987 assert!(
8988 validate_protocol_v2_fetch_request_features(
8989 &ProtocolV2FetchFeatures {
8990 ref_in_want: true,
8991 filter: true,
8992 ..ProtocolV2FetchFeatures::default()
8993 },
8994 &request,
8995 )
8996 .is_err()
8997 );
8998 }
8999
9000 #[test]
9001 fn protocol_v2_fetch_command_request_validates_against_handshake_features() {
9002 let handshake = TransportHandshake {
9003 protocol: ProtocolVersion::V2,
9004 capabilities: vec![
9005 Capability {
9006 name: "fetch".into(),
9007 value: Some("filter ref-in-want".into()),
9008 },
9009 Capability {
9010 name: "agent".into(),
9011 value: Some("sley/0".into()),
9012 },
9013 ],
9014 };
9015 let request = ProtocolV2CommandRequest {
9016 command: "fetch".into(),
9017 capabilities: vec![Capability {
9018 name: "agent".into(),
9019 value: Some("client/1".into()),
9020 }],
9021 arguments: vec![
9022 b"want-ref refs/heads/main".to_vec(),
9023 b"filter blob:none".to_vec(),
9024 ],
9025 };
9026 let fetch =
9027 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &request)
9028 .expect("test operation should succeed");
9029 assert_eq!(fetch.want_refs, vec!["refs/heads/main"]);
9030 assert_eq!(fetch.filter.as_deref(), Some("blob:none"));
9031
9032 let mut bad = request.clone();
9033 bad.arguments.push(b"sideband-all".to_vec());
9034 assert!(
9035 validate_protocol_v2_fetch_command_request(&handshake, ObjectFormat::Sha1, &bad)
9036 .is_err()
9037 );
9038 }
9039
9040 #[test]
9041 fn protocol_v2_object_info_request_parses_encodes_and_validates() {
9042 let oid = ObjectId::from_hex(
9043 ObjectFormat::Sha1,
9044 "1111111111111111111111111111111111111111",
9045 )
9046 .expect("test operation should succeed");
9047 let request = ProtocolV2CommandRequest {
9048 command: "object-info".into(),
9049 capabilities: Vec::new(),
9050 arguments: vec![
9051 b"size".to_vec(),
9052 b"oid 1111111111111111111111111111111111111111".to_vec(),
9053 ],
9054 };
9055 let parsed =
9056 ProtocolV2ObjectInfoRequest::from_command_request(ObjectFormat::Sha1, &request)
9057 .expect("test operation should succeed");
9058 assert_eq!(
9059 parsed,
9060 ProtocolV2ObjectInfoRequest {
9061 size: true,
9062 oids: vec![oid],
9063 }
9064 );
9065 assert_eq!(
9066 parsed
9067 .to_command_request()
9068 .expect("test operation should succeed"),
9069 request
9070 );
9071
9072 let handshake = TransportHandshake {
9073 protocol: ProtocolVersion::V2,
9074 capabilities: vec![Capability {
9075 name: "object-info".into(),
9076 value: None,
9077 }],
9078 };
9079 assert_eq!(
9080 validate_protocol_v2_object_info_command_request(
9081 &handshake,
9082 ObjectFormat::Sha1,
9083 &request,
9084 )
9085 .expect("test operation should succeed"),
9086 parsed
9087 );
9088 }
9089
9090 #[test]
9091 fn protocol_v2_object_info_request_streams_round_trip() {
9092 let request = ProtocolV2ObjectInfoRequest {
9093 size: true,
9094 oids: vec![
9095 ObjectId::from_hex(
9096 ObjectFormat::Sha1,
9097 "1111111111111111111111111111111111111111",
9098 )
9099 .expect("test operation should succeed"),
9100 ],
9101 };
9102 let mut encoded = Vec::new();
9103 write_protocol_v2_object_info_request(&mut encoded, &request)
9104 .expect("test operation should succeed");
9105 encoded.extend_from_slice(b"tail");
9106
9107 let mut input = encoded.as_slice();
9108 assert_eq!(
9109 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut input)
9110 .expect("test operation should succeed"),
9111 request
9112 );
9113 assert_eq!(input, b"tail");
9114 }
9115
9116 #[test]
9117 fn protocol_v2_object_info_request_rejects_malformed_arguments() {
9118 assert!(
9119 ProtocolV2ObjectInfoRequest::from_command_request(
9120 ObjectFormat::Sha1,
9121 &ProtocolV2CommandRequest {
9122 command: "object-info".into(),
9123 capabilities: Vec::new(),
9124 arguments: vec![b"oid 1111111111111111111111111111111111111111".to_vec()],
9125 },
9126 )
9127 .is_err()
9128 );
9129 assert!(
9130 ProtocolV2ObjectInfoRequest::from_command_request(
9131 ObjectFormat::Sha1,
9132 &ProtocolV2CommandRequest {
9133 command: "object-info".into(),
9134 capabilities: Vec::new(),
9135 arguments: vec![b"size".to_vec(), b"size".to_vec()],
9136 },
9137 )
9138 .is_err()
9139 );
9140 assert!(
9141 ProtocolV2ObjectInfoRequest::from_command_request(
9142 ObjectFormat::Sha1,
9143 &ProtocolV2CommandRequest {
9144 command: "object-info".into(),
9145 capabilities: Vec::new(),
9146 arguments: vec![b"size".to_vec()],
9147 },
9148 )
9149 .is_err()
9150 );
9151 assert!(
9152 ProtocolV2ObjectInfoRequest::from_command_request(
9153 ObjectFormat::Sha1,
9154 &ProtocolV2CommandRequest {
9155 command: "object-info".into(),
9156 capabilities: Vec::new(),
9157 arguments: vec![b"size".to_vec(), b"oid not-an-oid".to_vec()],
9158 },
9159 )
9160 .is_err()
9161 );
9162 assert!(
9163 validate_protocol_v2_object_info_command_request(
9164 &TransportHandshake {
9165 protocol: ProtocolVersion::V2,
9166 capabilities: Vec::new(),
9167 },
9168 ObjectFormat::Sha1,
9169 &ProtocolV2CommandRequest {
9170 command: "object-info".into(),
9171 capabilities: Vec::new(),
9172 arguments: vec![
9173 b"size".to_vec(),
9174 b"oid 1111111111111111111111111111111111111111".to_vec(),
9175 ],
9176 },
9177 )
9178 .is_err()
9179 );
9180 }
9181
9182 #[test]
9183 fn protocol_v2_command_request_classifies_known_and_unknown_commands() {
9184 let handshake = TransportHandshake {
9185 protocol: ProtocolVersion::V2,
9186 capabilities: vec![
9187 Capability {
9188 name: "ls-refs".into(),
9189 value: Some("unborn".into()),
9190 },
9191 Capability {
9192 name: "fetch".into(),
9193 value: Some("filter ref-in-want".into()),
9194 },
9195 Capability {
9196 name: "object-info".into(),
9197 value: None,
9198 },
9199 Capability {
9200 name: "server-option".into(),
9201 value: None,
9202 },
9203 Capability {
9204 name: "server-info".into(),
9205 value: Some("custom".into()),
9206 },
9207 ],
9208 };
9209 assert_eq!(
9210 classify_protocol_v2_command_request(
9211 &handshake,
9212 ObjectFormat::Sha1,
9213 &ProtocolV2CommandRequest {
9214 command: "ls-refs".into(),
9215 capabilities: Vec::new(),
9216 arguments: vec![b"unborn".to_vec()],
9217 },
9218 )
9219 .expect("test operation should succeed"),
9220 ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9221 unborn: true,
9222 ..ProtocolV2LsRefsRequest::default()
9223 })
9224 );
9225 assert_eq!(
9226 classify_protocol_v2_command_request(
9227 &handshake,
9228 ObjectFormat::Sha1,
9229 &ProtocolV2CommandRequest {
9230 command: "fetch".into(),
9231 capabilities: Vec::new(),
9232 arguments: vec![
9233 b"want-ref refs/heads/main".to_vec(),
9234 b"filter blob:none".to_vec(),
9235 ],
9236 },
9237 )
9238 .expect("test operation should succeed"),
9239 ProtocolV2Command::Fetch(ProtocolV2FetchRequest {
9240 want_refs: vec!["refs/heads/main".into()],
9241 filter: Some("blob:none".into()),
9242 ..ProtocolV2FetchRequest::default()
9243 })
9244 );
9245 assert_eq!(
9246 classify_protocol_v2_command_request(
9247 &handshake,
9248 ObjectFormat::Sha1,
9249 &ProtocolV2CommandRequest {
9250 command: "object-info".into(),
9251 capabilities: Vec::new(),
9252 arguments: vec![
9253 b"size".to_vec(),
9254 b"oid 1111111111111111111111111111111111111111".to_vec(),
9255 ],
9256 },
9257 )
9258 .expect("test operation should succeed"),
9259 ProtocolV2Command::ObjectInfo(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 );
9270
9271 let unknown = ProtocolV2CommandRequest {
9272 command: "server-info".into(),
9273 capabilities: vec![Capability {
9274 name: "server-option".into(),
9275 value: Some("trace=true".into()),
9276 }],
9277 arguments: Vec::new(),
9278 };
9279 assert_eq!(
9280 classify_protocol_v2_command_request(&handshake, ObjectFormat::Sha1, &unknown)
9281 .expect("test operation should succeed"),
9282 ProtocolV2Command::Unknown(unknown)
9283 );
9284 assert!(
9285 classify_protocol_v2_command_request(
9286 &handshake,
9287 ObjectFormat::Sha1,
9288 &ProtocolV2CommandRequest {
9289 command: "not-advertised".into(),
9290 capabilities: Vec::new(),
9291 arguments: Vec::new(),
9292 },
9293 )
9294 .is_err()
9295 );
9296 }
9297
9298 #[test]
9299 fn protocol_v2_session_request_classifies_streamed_command_and_done() {
9300 let handshake = TransportHandshake {
9301 protocol: ProtocolVersion::V2,
9302 capabilities: vec![
9303 Capability {
9304 name: "ls-refs".into(),
9305 value: Some("unborn".into()),
9306 },
9307 Capability {
9308 name: "fetch".into(),
9309 value: Some("filter ref-in-want".into()),
9310 },
9311 ],
9312 };
9313 let command = ProtocolV2Request::Command(ProtocolV2CommandRequest {
9314 command: "ls-refs".into(),
9315 capabilities: Vec::new(),
9316 arguments: vec![b"unborn".to_vec()],
9317 });
9318 assert_eq!(
9319 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &command)
9320 .expect("test operation should succeed"),
9321 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9322 unborn: true,
9323 ..ProtocolV2LsRefsRequest::default()
9324 }))
9325 );
9326 assert_eq!(
9327 classify_protocol_v2_request(&handshake, ObjectFormat::Sha1, &ProtocolV2Request::Done)
9328 .expect("test operation should succeed"),
9329 ProtocolV2SessionRequest::Done
9330 );
9331
9332 let mut encoded = Vec::new();
9333 write_protocol_v2_request(&mut encoded, &command).expect("test operation should succeed");
9334 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
9335 .expect("test operation should succeed");
9336 encoded.extend_from_slice(b"tail");
9337
9338 let mut input = encoded.as_slice();
9339 assert_eq!(
9340 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9341 .expect("test operation should succeed"),
9342 ProtocolV2SessionRequest::Command(ProtocolV2Command::LsRefs(ProtocolV2LsRefsRequest {
9343 unborn: true,
9344 ..ProtocolV2LsRefsRequest::default()
9345 }))
9346 );
9347 assert_eq!(
9348 read_protocol_v2_session_request(&handshake, ObjectFormat::Sha1, &mut input)
9349 .expect("test operation should succeed"),
9350 ProtocolV2SessionRequest::Done
9351 );
9352 assert_eq!(input, b"tail");
9353 }
9354
9355 #[test]
9356 fn advertised_ref_parses_first_v0_capability_line() {
9357 let payload =
9358 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 HEAD\0multi_ack symref=HEAD:refs/heads/main\n";
9359 let advertisement = parse_ref_advertisement(ObjectFormat::Sha1, payload)
9360 .expect("test operation should succeed");
9361 assert_eq!(
9362 advertisement.oid,
9363 ObjectId::from_hex(
9364 ObjectFormat::Sha1,
9365 "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
9366 )
9367 .expect("test operation should succeed")
9368 );
9369 assert_eq!(advertisement.name, "HEAD");
9370 assert_eq!(
9371 advertisement.capabilities,
9372 vec![
9373 Capability {
9374 name: "multi_ack".into(),
9375 value: None,
9376 },
9377 Capability {
9378 name: "symref".into(),
9379 value: Some("HEAD:refs/heads/main".into()),
9380 },
9381 ]
9382 );
9383 }
9384
9385 #[test]
9386 fn advertised_ref_parses_lines_without_capabilities() {
9387 let advertisement = parse_ref_advertisement(
9388 ObjectFormat::Sha1,
9389 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 refs/heads/main\n",
9390 )
9391 .expect("test operation should succeed");
9392 assert_eq!(advertisement.name, "refs/heads/main");
9393 assert!(advertisement.capabilities.is_empty());
9394 }
9395
9396 #[test]
9397 fn advertised_ref_rejects_malformed_payloads() {
9398 assert!(
9399 parse_ref_advertisement(ObjectFormat::Sha1, b"not-an-oid refs/heads/main\n").is_err()
9400 );
9401 assert!(
9402 parse_ref_advertisement(
9403 ObjectFormat::Sha1,
9404 b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n"
9405 )
9406 .is_err()
9407 );
9408 }
9409
9410 #[test]
9411 fn advertised_refs_parse_and_encode_stream() {
9412 let main = ObjectId::from_hex(
9413 ObjectFormat::Sha1,
9414 "1111111111111111111111111111111111111111",
9415 )
9416 .expect("test operation should succeed");
9417 let feature = ObjectId::from_hex(
9418 ObjectFormat::Sha1,
9419 "2222222222222222222222222222222222222222",
9420 )
9421 .expect("test operation should succeed");
9422 let frames = vec![
9423 PktLineFrame::Data(
9424 b"1111111111111111111111111111111111111111 HEAD\0multi_ack thin-pack agent=git/2.54.0\n"
9425 .to_vec(),
9426 ),
9427 PktLineFrame::Data(
9428 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9429 ),
9430 PktLineFrame::Flush,
9431 ];
9432 let advertisements = parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9433 .expect("test operation should succeed");
9434 assert_eq!(
9435 advertisements,
9436 vec![
9437 RefAdvertisement {
9438 oid: main,
9439 name: "HEAD".into(),
9440 capabilities: vec![
9441 Capability {
9442 name: "multi_ack".into(),
9443 value: None,
9444 },
9445 Capability {
9446 name: "thin-pack".into(),
9447 value: None,
9448 },
9449 Capability {
9450 name: "agent".into(),
9451 value: Some("git/2.54.0".into()),
9452 },
9453 ],
9454 },
9455 RefAdvertisement {
9456 oid: feature,
9457 name: "refs/heads/feature".into(),
9458 capabilities: Vec::new(),
9459 },
9460 ]
9461 );
9462 assert_eq!(
9463 encode_ref_advertisements(&advertisements).expect("test operation should succeed"),
9464 frames
9465 );
9466 assert_eq!(
9467 parse_ref_advertisements(ObjectFormat::Sha1, &[PktLineFrame::Flush])
9468 .expect("test operation should succeed"),
9469 Vec::<RefAdvertisement>::new()
9470 );
9471 }
9472
9473 #[test]
9474 fn advertised_ref_set_parses_v1_version_refs_and_shallow() {
9475 let main = ObjectId::from_hex(
9476 ObjectFormat::Sha1,
9477 "1111111111111111111111111111111111111111",
9478 )
9479 .expect("test operation should succeed");
9480 let feature = ObjectId::from_hex(
9481 ObjectFormat::Sha1,
9482 "2222222222222222222222222222222222222222",
9483 )
9484 .expect("test operation should succeed");
9485 let shallow = ObjectId::from_hex(
9486 ObjectFormat::Sha1,
9487 "3333333333333333333333333333333333333333",
9488 )
9489 .expect("test operation should succeed");
9490 let frames = vec![
9491 PktLineFrame::Data(b"version 1\n".to_vec()),
9492 PktLineFrame::Data(
9493 b"1111111111111111111111111111111111111111 HEAD\0multi_ack symref=HEAD:refs/heads/main\n"
9494 .to_vec(),
9495 ),
9496 PktLineFrame::Data(
9497 b"2222222222222222222222222222222222222222 refs/heads/feature\n".to_vec(),
9498 ),
9499 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
9500 PktLineFrame::Flush,
9501 ];
9502
9503 let set = parse_ref_advertisement_set(ObjectFormat::Sha1, &frames)
9504 .expect("test operation should succeed");
9505 assert_eq!(set.protocol, ProtocolVersion::V1);
9506 assert_eq!(set.shallow, vec![shallow]);
9507 assert_eq!(
9508 set.refs,
9509 vec![
9510 RefAdvertisement {
9511 oid: main,
9512 name: "HEAD".into(),
9513 capabilities: vec![
9514 Capability {
9515 name: "multi_ack".into(),
9516 value: None,
9517 },
9518 Capability {
9519 name: "symref".into(),
9520 value: Some("HEAD:refs/heads/main".into()),
9521 },
9522 ],
9523 },
9524 RefAdvertisement {
9525 oid: feature,
9526 name: "refs/heads/feature".into(),
9527 capabilities: Vec::new(),
9528 },
9529 ]
9530 );
9531 assert_eq!(
9532 parse_ref_advertisements(ObjectFormat::Sha1, &frames)
9533 .expect("test operation should succeed"),
9534 set.refs
9535 );
9536 assert_eq!(
9537 encode_ref_advertisement_set(&set).expect("test operation should succeed"),
9538 frames
9539 );
9540 }
9541
9542 #[test]
9543 fn advertised_refs_streams_round_trip() {
9544 let advertisements = vec![RefAdvertisement {
9545 oid: ObjectId::from_hex(
9546 ObjectFormat::Sha1,
9547 "1111111111111111111111111111111111111111",
9548 )
9549 .expect("test operation should succeed"),
9550 name: "HEAD".into(),
9551 capabilities: vec![Capability {
9552 name: "symref".into(),
9553 value: Some("HEAD:refs/heads/main".into()),
9554 }],
9555 }];
9556 let mut encoded = Vec::new();
9557 write_ref_advertisements(&mut encoded, &advertisements)
9558 .expect("test operation should succeed");
9559 encoded.extend_from_slice(b"tail");
9560
9561 let mut input = encoded.as_slice();
9562 assert_eq!(
9563 read_ref_advertisements(ObjectFormat::Sha1, &mut input)
9564 .expect("test operation should succeed"),
9565 advertisements
9566 );
9567 assert_eq!(input, b"tail");
9568 }
9569
9570 #[test]
9571 fn advertised_ref_set_streams_round_trip() {
9572 let set = RefAdvertisementSet {
9573 protocol: ProtocolVersion::V1,
9574 refs: vec![RefAdvertisement {
9575 oid: ObjectId::from_hex(
9576 ObjectFormat::Sha1,
9577 "1111111111111111111111111111111111111111",
9578 )
9579 .expect("test operation should succeed"),
9580 name: "HEAD".into(),
9581 capabilities: vec![Capability {
9582 name: "symref".into(),
9583 value: Some("HEAD:refs/heads/main".into()),
9584 }],
9585 }],
9586 shallow: vec![
9587 ObjectId::from_hex(
9588 ObjectFormat::Sha1,
9589 "2222222222222222222222222222222222222222",
9590 )
9591 .expect("test operation should succeed"),
9592 ],
9593 };
9594 let mut encoded = Vec::new();
9595 write_ref_advertisement_set(&mut encoded, &set).expect("test operation should succeed");
9596 encoded.extend_from_slice(b"tail");
9597
9598 let mut input = encoded.as_slice();
9599 assert_eq!(
9600 read_ref_advertisement_set(ObjectFormat::Sha1, &mut input)
9601 .expect("test operation should succeed"),
9602 set
9603 );
9604 assert_eq!(input, b"tail");
9605 }
9606
9607 #[test]
9608 fn advertised_refs_reject_malformed_streams() {
9609 assert!(
9610 parse_ref_advertisements(
9611 ObjectFormat::Sha1,
9612 &[PktLineFrame::Data(
9613 b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),
9614 )],
9615 )
9616 .is_err()
9617 );
9618 assert!(
9619 parse_ref_advertisements(
9620 ObjectFormat::Sha1,
9621 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
9622 )
9623 .is_err()
9624 );
9625 assert!(parse_ref_advertisements(
9626 ObjectFormat::Sha1,
9627 &[
9628 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9629 PktLineFrame::Data(
9630 b"2222222222222222222222222222222222222222 refs/heads/main\0thin-pack\n"
9631 .to_vec(),
9632 ),
9633 PktLineFrame::Flush,
9634 ],
9635 )
9636 .is_err());
9637 assert!(parse_ref_advertisement_set(
9638 ObjectFormat::Sha1,
9639 &[
9640 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9641 PktLineFrame::Data(b"version 1\n".to_vec()),
9642 PktLineFrame::Flush,
9643 ],
9644 )
9645 .is_err());
9646 assert!(
9647 parse_ref_advertisement_set(
9648 ObjectFormat::Sha1,
9649 &[
9650 PktLineFrame::Data(b"version 2\n".to_vec()),
9651 PktLineFrame::Flush,
9652 ],
9653 )
9654 .is_err()
9655 );
9656 assert!(
9657 parse_ref_advertisement_set(
9658 ObjectFormat::Sha1,
9659 &[
9660 PktLineFrame::Data(
9661 b"shallow 1111111111111111111111111111111111111111\n".to_vec()
9662 ),
9663 PktLineFrame::Flush,
9664 ],
9665 )
9666 .is_err()
9667 );
9668 assert!(parse_ref_advertisement_set(
9669 ObjectFormat::Sha1,
9670 &[
9671 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9672 PktLineFrame::Data(b"shallow not-an-oid\n".to_vec()),
9673 PktLineFrame::Flush,
9674 ],
9675 )
9676 .is_err());
9677 assert!(parse_ref_advertisement_set(
9678 ObjectFormat::Sha1,
9679 &[
9680 PktLineFrame::Data(b"1111111111111111111111111111111111111111 HEAD\n".to_vec(),),
9681 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
9682 PktLineFrame::Data(
9683 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
9684 ),
9685 PktLineFrame::Flush,
9686 ],
9687 )
9688 .is_err());
9689 assert!(
9690 encode_ref_advertisements(&[
9691 RefAdvertisement {
9692 oid: ObjectId::from_hex(
9693 ObjectFormat::Sha1,
9694 "1111111111111111111111111111111111111111",
9695 )
9696 .expect("test operation should succeed"),
9697 name: "HEAD".into(),
9698 capabilities: Vec::new(),
9699 },
9700 RefAdvertisement {
9701 oid: ObjectId::from_hex(
9702 ObjectFormat::Sha1,
9703 "2222222222222222222222222222222222222222",
9704 )
9705 .expect("test operation should succeed"),
9706 name: "refs/heads/main".into(),
9707 capabilities: vec![Capability {
9708 name: "thin-pack".into(),
9709 value: None,
9710 }],
9711 },
9712 ])
9713 .is_err()
9714 );
9715 assert!(
9716 encode_ref_advertisement(&RefAdvertisement {
9717 oid: ObjectId::from_hex(
9718 ObjectFormat::Sha1,
9719 "1111111111111111111111111111111111111111",
9720 )
9721 .expect("test operation should succeed"),
9722 name: "bad ref".into(),
9723 capabilities: Vec::new(),
9724 })
9725 .is_err()
9726 );
9727 assert!(
9728 encode_ref_advertisement_set(&RefAdvertisementSet {
9729 protocol: ProtocolVersion::V2,
9730 refs: Vec::new(),
9731 shallow: Vec::new(),
9732 })
9733 .is_err()
9734 );
9735 assert!(
9736 encode_ref_advertisement_set(&RefAdvertisementSet {
9737 protocol: ProtocolVersion::V0,
9738 refs: Vec::new(),
9739 shallow: vec![
9740 ObjectId::from_hex(
9741 ObjectFormat::Sha1,
9742 "1111111111111111111111111111111111111111",
9743 )
9744 .expect("test operation should succeed")
9745 ],
9746 })
9747 .is_err()
9748 );
9749 }
9750
9751 #[test]
9752 fn dumb_http_info_refs_parse_and_encode_records() {
9753 let main = ObjectId::from_hex(
9754 ObjectFormat::Sha1,
9755 "1111111111111111111111111111111111111111",
9756 )
9757 .expect("test operation should succeed");
9758 let tag = ObjectId::from_hex(
9759 ObjectFormat::Sha1,
9760 "2222222222222222222222222222222222222222",
9761 )
9762 .expect("test operation should succeed");
9763 let peeled = ObjectId::from_hex(
9764 ObjectFormat::Sha1,
9765 "3333333333333333333333333333333333333333",
9766 )
9767 .expect("test operation should succeed");
9768 let input = b"1111111111111111111111111111111111111111\trefs/heads/main\n2222222222222222222222222222222222222222\trefs/tags/v1.0\n3333333333333333333333333333333333333333\trefs/tags/v1.0^{}\n";
9769
9770 let records = parse_dumb_http_info_refs(ObjectFormat::Sha1, input)
9771 .expect("test operation should succeed");
9772 assert_eq!(
9773 records,
9774 vec![
9775 DumbHttpRefRecord {
9776 oid: main,
9777 name: "refs/heads/main".into(),
9778 peeled: false,
9779 },
9780 DumbHttpRefRecord {
9781 oid: tag,
9782 name: "refs/tags/v1.0".into(),
9783 peeled: false,
9784 },
9785 DumbHttpRefRecord {
9786 oid: peeled,
9787 name: "refs/tags/v1.0".into(),
9788 peeled: true,
9789 },
9790 ]
9791 );
9792 assert_eq!(
9793 encode_dumb_http_info_refs(&records).expect("test operation should succeed"),
9794 input
9795 );
9796 assert_eq!(
9797 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"")
9798 .expect("test operation should succeed"),
9799 Vec::<DumbHttpRefRecord>::new()
9800 );
9801 }
9802
9803 #[test]
9804 fn dumb_http_info_refs_streams_round_trip() {
9805 let records = vec![DumbHttpRefRecord {
9806 oid: ObjectId::from_hex(
9807 ObjectFormat::Sha1,
9808 "1111111111111111111111111111111111111111",
9809 )
9810 .expect("test operation should succeed"),
9811 name: "refs/heads/main".into(),
9812 peeled: false,
9813 }];
9814 let mut encoded = Vec::new();
9815 write_dumb_http_info_refs(&mut encoded, &records).expect("test operation should succeed");
9816 let mut input = encoded.as_slice();
9817 assert_eq!(
9818 read_dumb_http_info_refs(ObjectFormat::Sha1, &mut input)
9819 .expect("test operation should succeed"),
9820 records
9821 );
9822 assert!(input.is_empty());
9823 }
9824
9825 #[test]
9826 fn dumb_http_info_refs_reject_malformed_records() {
9827 assert!(
9828 parse_dumb_http_info_refs(
9829 ObjectFormat::Sha1,
9830 b"1111111111111111111111111111111111111111 refs/heads/main\n",
9831 )
9832 .is_err()
9833 );
9834 assert!(
9835 parse_dumb_http_info_refs(
9836 ObjectFormat::Sha1,
9837 b"1111111111111111111111111111111111111111\trefs/heads/main",
9838 )
9839 .is_err()
9840 );
9841 assert!(
9842 parse_dumb_http_info_refs(ObjectFormat::Sha1, b"not-an-oid\trefs/heads/main\n")
9843 .is_err()
9844 );
9845 assert!(
9846 parse_dumb_http_info_refs(
9847 ObjectFormat::Sha1,
9848 b"1111111111111111111111111111111111111111\tbad ref\n",
9849 )
9850 .is_err()
9851 );
9852 assert!(
9853 encode_dumb_http_info_refs(&[DumbHttpRefRecord {
9854 oid: ObjectId::from_hex(
9855 ObjectFormat::Sha1,
9856 "1111111111111111111111111111111111111111",
9857 )
9858 .expect("test operation should succeed"),
9859 name: "refs/tags/v1.0^{}".into(),
9860 peeled: false,
9861 }])
9862 .is_err()
9863 );
9864 }
9865
9866 #[test]
9867 fn dumb_http_alternates_parse_and_encode_locations() {
9868 let input = b"https://example.com/base.git/objects/\n../other.git/objects/\n";
9869 let alternates = parse_dumb_http_alternates(input).expect("test operation should succeed");
9870 assert_eq!(
9871 alternates,
9872 vec![
9873 "https://example.com/base.git/objects/".to_string(),
9874 "../other.git/objects/".to_string(),
9875 ]
9876 );
9877 assert_eq!(
9878 encode_dumb_http_alternates(&alternates).expect("test operation should succeed"),
9879 input
9880 );
9881 assert_eq!(
9882 parse_dumb_http_alternates(b"").expect("test operation should succeed"),
9883 Vec::<String>::new()
9884 );
9885 }
9886
9887 #[test]
9888 fn dumb_http_alternates_streams_round_trip() {
9889 let alternates = vec!["https://example.com/base.git/objects/".to_string()];
9890 let mut encoded = Vec::new();
9891 write_dumb_http_alternates(&mut encoded, &alternates)
9892 .expect("test operation should succeed");
9893 let mut input = encoded.as_slice();
9894 assert_eq!(
9895 read_dumb_http_alternates(&mut input).expect("test operation should succeed"),
9896 alternates
9897 );
9898 assert!(input.is_empty());
9899 }
9900
9901 #[test]
9902 fn dumb_http_alternates_reject_malformed_lines() {
9903 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/").is_err());
9904 assert!(parse_dumb_http_alternates(b"\n").is_err());
9905 assert!(parse_dumb_http_alternates(b"https://example.com/base.git/objects/\r\n").is_err());
9906 assert!(encode_dumb_http_alternates(&["bad\nalternate".to_string()]).is_err());
9907 }
9908
9909 #[test]
9910 fn dumb_http_packs_parse_and_encode_pack_records() {
9911 let first = ObjectId::from_hex(
9912 ObjectFormat::Sha1,
9913 "1111111111111111111111111111111111111111",
9914 )
9915 .expect("test operation should succeed");
9916 let second = ObjectId::from_hex(
9917 ObjectFormat::Sha1,
9918 "2222222222222222222222222222222222222222",
9919 )
9920 .expect("test operation should succeed");
9921 let input = b"P pack-1111111111111111111111111111111111111111.pack\nP pack-2222222222222222222222222222222222222222.pack\n";
9922 let records = parse_dumb_http_packs(ObjectFormat::Sha1, input)
9923 .expect("test operation should succeed");
9924 assert_eq!(
9925 records,
9926 vec![
9927 DumbHttpPackRecord { hash: first },
9928 DumbHttpPackRecord { hash: second },
9929 ]
9930 );
9931 assert_eq!(
9932 encode_dumb_http_packs(&records).expect("test operation should succeed"),
9933 input
9934 );
9935 assert_eq!(
9936 parse_dumb_http_packs(ObjectFormat::Sha1, b"").expect("test operation should succeed"),
9937 Vec::<DumbHttpPackRecord>::new()
9938 );
9939 }
9940
9941 #[test]
9942 fn dumb_http_packs_streams_round_trip() {
9943 let records = vec![DumbHttpPackRecord {
9944 hash: ObjectId::from_hex(
9945 ObjectFormat::Sha1,
9946 "1111111111111111111111111111111111111111",
9947 )
9948 .expect("test operation should succeed"),
9949 }];
9950 let mut encoded = Vec::new();
9951 write_dumb_http_packs(&mut encoded, &records).expect("test operation should succeed");
9952 let mut input = encoded.as_slice();
9953 assert_eq!(
9954 read_dumb_http_packs(ObjectFormat::Sha1, &mut input)
9955 .expect("test operation should succeed"),
9956 records
9957 );
9958 assert!(input.is_empty());
9959 }
9960
9961 #[test]
9962 fn dumb_http_packs_reject_malformed_records() {
9963 assert!(
9964 parse_dumb_http_packs(
9965 ObjectFormat::Sha1,
9966 b"P pack-1111111111111111111111111111111111111111.pack",
9967 )
9968 .is_err()
9969 );
9970 assert!(
9971 parse_dumb_http_packs(
9972 ObjectFormat::Sha1,
9973 b"pack-1111111111111111111111111111111111111111.pack\n",
9974 )
9975 .is_err()
9976 );
9977 assert!(parse_dumb_http_packs(ObjectFormat::Sha1, b"P pack-not-a-hash.pack\n",).is_err());
9978 assert!(
9979 parse_dumb_http_packs(
9980 ObjectFormat::Sha1,
9981 b"P pack-1111111111111111111111111111111111111111.idx\n",
9982 )
9983 .is_err()
9984 );
9985 }
9986
9987 #[test]
9988 fn upload_pack_features_parse_encode_and_validate_request() {
9989 let capabilities = vec![
9990 Capability {
9991 name: "multi_ack".into(),
9992 value: None,
9993 },
9994 Capability {
9995 name: "multi_ack_detailed".into(),
9996 value: None,
9997 },
9998 Capability {
9999 name: "no-done".into(),
10000 value: None,
10001 },
10002 Capability {
10003 name: "thin-pack".into(),
10004 value: None,
10005 },
10006 Capability {
10007 name: "side-band-64k".into(),
10008 value: None,
10009 },
10010 Capability {
10011 name: "ofs-delta".into(),
10012 value: None,
10013 },
10014 Capability {
10015 name: "shallow".into(),
10016 value: None,
10017 },
10018 Capability {
10019 name: "deepen-since".into(),
10020 value: None,
10021 },
10022 Capability {
10023 name: "deepen-not".into(),
10024 value: None,
10025 },
10026 Capability {
10027 name: "include-tag".into(),
10028 value: None,
10029 },
10030 Capability {
10031 name: "no-progress".into(),
10032 value: None,
10033 },
10034 Capability {
10035 name: "filter".into(),
10036 value: None,
10037 },
10038 Capability {
10039 name: "agent".into(),
10040 value: Some("git/2.54.0".into()),
10041 },
10042 Capability {
10043 name: "object-format".into(),
10044 value: Some("sha256".into()),
10045 },
10046 Capability {
10047 name: "symref".into(),
10048 value: Some("HEAD:refs/heads/main".into()),
10049 },
10050 Capability {
10051 name: "custom".into(),
10052 value: Some("value".into()),
10053 },
10054 ];
10055 let features =
10056 parse_upload_pack_features(&capabilities).expect("test operation should succeed");
10057 assert_eq!(
10058 features,
10059 UploadPackFeatures {
10060 multi_ack: true,
10061 multi_ack_detailed: true,
10062 no_done: true,
10063 thin_pack: true,
10064 side_band_64k: true,
10065 ofs_delta: true,
10066 shallow: true,
10067 deepen_since: true,
10068 deepen_not: true,
10069 include_tag: true,
10070 no_progress: true,
10071 filter: true,
10072 agent: Some("git/2.54.0".into()),
10073 object_format: Some(ObjectFormat::Sha256),
10074 symrefs: vec!["HEAD:refs/heads/main".into()],
10075 unknown: vec![Capability {
10076 name: "custom".into(),
10077 value: Some("value".into()),
10078 }],
10079 ..UploadPackFeatures::default()
10080 }
10081 );
10082 assert_eq!(
10083 encode_upload_pack_features(&features).expect("test operation should succeed"),
10084 capabilities
10085 );
10086
10087 let request = UploadPackRequest {
10088 wants: vec![
10089 ObjectId::from_hex(
10090 ObjectFormat::Sha1,
10091 "1111111111111111111111111111111111111111",
10092 )
10093 .expect("test operation should succeed"),
10094 ],
10095 capabilities: vec![
10096 Capability {
10097 name: "multi_ack_detailed".into(),
10098 value: None,
10099 },
10100 Capability {
10101 name: "thin-pack".into(),
10102 value: None,
10103 },
10104 Capability {
10105 name: "side-band-64k".into(),
10106 value: None,
10107 },
10108 Capability {
10109 name: "ofs-delta".into(),
10110 value: None,
10111 },
10112 Capability {
10113 name: "include-tag".into(),
10114 value: None,
10115 },
10116 Capability {
10117 name: "agent".into(),
10118 value: Some("sley".into()),
10119 },
10120 ],
10121 shallow: vec![
10122 ObjectId::from_hex(
10123 ObjectFormat::Sha1,
10124 "2222222222222222222222222222222222222222",
10125 )
10126 .expect("test operation should succeed"),
10127 ],
10128 deepen: Some(5),
10129 deepen_since: Some(1_710_000_000),
10130 deepen_not: vec!["refs/tags/base".into()],
10131 filter: Some("blob:none".into()),
10132 };
10133 validate_upload_pack_request_features(&features, &request)
10134 .expect("test operation should succeed");
10135 }
10136
10137 #[test]
10138 fn upload_pack_features_reject_invalid_requests() {
10139 let want = ObjectId::from_hex(
10140 ObjectFormat::Sha1,
10141 "1111111111111111111111111111111111111111",
10142 )
10143 .expect("test operation should succeed");
10144 let features = UploadPackFeatures {
10145 thin_pack: true,
10146 side_band: true,
10147 ..UploadPackFeatures::default()
10148 };
10149
10150 assert!(
10151 validate_upload_pack_request_features(
10152 &features,
10153 &UploadPackRequest {
10154 wants: vec![want],
10155 capabilities: vec![Capability {
10156 name: "ofs-delta".into(),
10157 value: None,
10158 }],
10159 ..UploadPackRequest::default()
10160 },
10161 )
10162 .is_err()
10163 );
10164 assert!(
10165 validate_upload_pack_request_features(
10166 &features,
10167 &UploadPackRequest {
10168 wants: vec![want],
10169 shallow: vec![want],
10170 ..UploadPackRequest::default()
10171 },
10172 )
10173 .is_err()
10174 );
10175 assert!(
10176 validate_upload_pack_request_features(
10177 &features,
10178 &UploadPackRequest {
10179 wants: vec![want],
10180 filter: Some("blob:none".into()),
10181 ..UploadPackRequest::default()
10182 },
10183 )
10184 .is_err()
10185 );
10186 assert!(
10187 validate_upload_pack_request_features(
10188 &UploadPackFeatures {
10189 side_band: true,
10190 side_band_64k: true,
10191 ..UploadPackFeatures::default()
10192 },
10193 &UploadPackRequest {
10194 wants: vec![want],
10195 capabilities: vec![
10196 Capability {
10197 name: "side-band".into(),
10198 value: None,
10199 },
10200 Capability {
10201 name: "side-band-64k".into(),
10202 value: None,
10203 },
10204 ],
10205 ..UploadPackRequest::default()
10206 },
10207 )
10208 .is_err()
10209 );
10210
10211 assert!(
10212 parse_upload_pack_features(&[
10213 Capability {
10214 name: "thin-pack".into(),
10215 value: None,
10216 },
10217 Capability {
10218 name: "thin-pack".into(),
10219 value: None,
10220 },
10221 ])
10222 .is_err()
10223 );
10224 assert!(
10225 encode_upload_pack_features(&UploadPackFeatures {
10226 unknown: vec![Capability {
10227 name: "filter".into(),
10228 value: None,
10229 }],
10230 ..UploadPackFeatures::default()
10231 })
10232 .is_err()
10233 );
10234 }
10235
10236 #[test]
10237 fn upload_pack_raw_response_builder_filters_unknown_haves_and_builds_pack() {
10238 let want = ObjectId::from_hex(
10239 ObjectFormat::Sha1,
10240 "1111111111111111111111111111111111111111",
10241 )
10242 .expect("test operation should succeed");
10243 let known_have = ObjectId::from_hex(
10244 ObjectFormat::Sha1,
10245 "2222222222222222222222222222222222222222",
10246 )
10247 .expect("test operation should succeed");
10248 let unknown_have = ObjectId::from_hex(
10249 ObjectFormat::Sha1,
10250 "3333333333333333333333333333333333333333",
10251 )
10252 .expect("test operation should succeed");
10253 let existing = std::collections::HashSet::from([want, known_have]);
10254
10255 let response = build_upload_pack_raw_packfile_response(
10256 &UploadPackFeatures::default(),
10257 UploadPackRequest {
10258 wants: vec![want],
10259 ..UploadPackRequest::default()
10260 },
10261 [known_have, unknown_have],
10262 |oid| Ok(existing.contains(oid)),
10263 |wants, haves| {
10264 assert_eq!(wants, vec![want]);
10265 assert_eq!(haves, vec![known_have]);
10266 Ok(Some(b"PACKmock".to_vec()))
10267 },
10268 )
10269 .expect("test operation should succeed");
10270
10271 assert_eq!(
10272 response.acknowledgments,
10273 vec![UploadPackAcknowledgment::Nak]
10274 );
10275 assert_eq!(response.packfile, b"PACKmock");
10276 }
10277
10278 #[test]
10279 fn upload_pack_raw_response_builder_rejects_missing_want_and_empty_pack() {
10280 let want = ObjectId::from_hex(
10281 ObjectFormat::Sha1,
10282 "1111111111111111111111111111111111111111",
10283 )
10284 .expect("test operation should succeed");
10285
10286 assert!(
10287 build_upload_pack_raw_packfile_response(
10288 &UploadPackFeatures::default(),
10289 UploadPackRequest {
10290 wants: vec![want],
10291 ..UploadPackRequest::default()
10292 },
10293 Vec::<ObjectId>::new(),
10294 |_| Ok(false),
10295 |_, _| Ok(Some(b"PACKmock".to_vec())),
10296 )
10297 .is_err()
10298 );
10299
10300 assert!(
10301 build_upload_pack_raw_packfile_response(
10302 &UploadPackFeatures::default(),
10303 UploadPackRequest {
10304 wants: vec![want],
10305 ..UploadPackRequest::default()
10306 },
10307 Vec::<ObjectId>::new(),
10308 |_| Ok(true),
10309 |_, _| Ok(None),
10310 )
10311 .is_err()
10312 );
10313 }
10314
10315 #[test]
10316 fn upload_pack_request_parses_and_encodes_initial_fetch_request() {
10317 let want = ObjectId::from_hex(
10318 ObjectFormat::Sha1,
10319 "1111111111111111111111111111111111111111",
10320 )
10321 .expect("test operation should succeed");
10322 let second_want = ObjectId::from_hex(
10323 ObjectFormat::Sha1,
10324 "2222222222222222222222222222222222222222",
10325 )
10326 .expect("test operation should succeed");
10327 let shallow = ObjectId::from_hex(
10328 ObjectFormat::Sha1,
10329 "3333333333333333333333333333333333333333",
10330 )
10331 .expect("test operation should succeed");
10332 let frames = vec![
10333 PktLineFrame::Data(
10334 b"want 1111111111111111111111111111111111111111 multi_ack thin-pack agent=git/2.54.0\n"
10335 .to_vec(),
10336 ),
10337 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10338 PktLineFrame::Data(b"shallow 3333333333333333333333333333333333333333\n".to_vec()),
10339 PktLineFrame::Data(b"deepen-since 1710000000\n".to_vec()),
10340 PktLineFrame::Data(b"deepen-not refs/tags/base\n".to_vec()),
10341 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10342 PktLineFrame::Flush,
10343 ];
10344 let request = parse_upload_pack_request(ObjectFormat::Sha1, &frames)
10345 .expect("test operation should succeed")
10346 .expect("test operation should succeed");
10347 assert_eq!(
10348 request,
10349 UploadPackRequest {
10350 wants: vec![want, second_want],
10351 capabilities: vec![
10352 Capability {
10353 name: "multi_ack".into(),
10354 value: None,
10355 },
10356 Capability {
10357 name: "thin-pack".into(),
10358 value: None,
10359 },
10360 Capability {
10361 name: "agent".into(),
10362 value: Some("git/2.54.0".into()),
10363 },
10364 ],
10365 shallow: vec![shallow],
10366 deepen: None,
10367 deepen_since: Some(1_710_000_000),
10368 deepen_not: vec!["refs/tags/base".into()],
10369 filter: Some("blob:none".into()),
10370 }
10371 );
10372 assert_eq!(
10373 encode_upload_pack_request(Some(&request)).expect("test operation should succeed"),
10374 frames
10375 );
10376 assert_eq!(
10377 parse_upload_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10378 .expect("test operation should succeed"),
10379 None
10380 );
10381 assert_eq!(
10382 encode_upload_pack_request(None).expect("test operation should succeed"),
10383 vec![PktLineFrame::Flush]
10384 );
10385 }
10386
10387 #[test]
10388 fn upload_pack_request_streams_round_trip() {
10389 let request = UploadPackRequest {
10390 wants: vec![
10391 ObjectId::from_hex(
10392 ObjectFormat::Sha1,
10393 "1111111111111111111111111111111111111111",
10394 )
10395 .expect("test operation should succeed"),
10396 ],
10397 capabilities: vec![Capability {
10398 name: "ofs-delta".into(),
10399 value: None,
10400 }],
10401 deepen: Some(10),
10402 ..UploadPackRequest::default()
10403 };
10404 let mut encoded = Vec::new();
10405 write_upload_pack_request(&mut encoded, Some(&request))
10406 .expect("test operation should succeed");
10407 encoded.extend_from_slice(b"tail");
10408
10409 let mut input = encoded.as_slice();
10410 assert_eq!(
10411 read_upload_pack_request(ObjectFormat::Sha1, &mut input)
10412 .expect("test operation should succeed"),
10413 Some(request)
10414 );
10415 assert_eq!(input, b"tail");
10416 }
10417
10418 #[test]
10419 fn upload_pack_request_rejects_malformed_requests() {
10420 assert!(
10421 parse_upload_pack_request(
10422 ObjectFormat::Sha1,
10423 &[PktLineFrame::Data(
10424 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10425 )],
10426 )
10427 .is_err()
10428 );
10429 assert!(
10430 parse_upload_pack_request(
10431 ObjectFormat::Sha1,
10432 &[
10433 PktLineFrame::Data(
10434 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10435 ),
10436 PktLineFrame::Flush,
10437 ],
10438 )
10439 .is_err()
10440 );
10441 assert!(
10442 parse_upload_pack_request(
10443 ObjectFormat::Sha1,
10444 &[
10445 PktLineFrame::Data(
10446 b"want 1111111111111111111111111111111111111111 thin-pack\n".to_vec(),
10447 ),
10448 PktLineFrame::Data(
10449 b"want 2222222222222222222222222222222222222222 ofs-delta\n".to_vec(),
10450 ),
10451 PktLineFrame::Flush,
10452 ],
10453 )
10454 .is_err()
10455 );
10456 assert!(parse_upload_pack_request(
10457 ObjectFormat::Sha1,
10458 &[
10459 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10460 PktLineFrame::Data(b"deepen 1\n".to_vec()),
10461 PktLineFrame::Data(b"want 2222222222222222222222222222222222222222\n".to_vec()),
10462 PktLineFrame::Flush,
10463 ],
10464 )
10465 .is_err());
10466 assert!(parse_upload_pack_request(
10467 ObjectFormat::Sha1,
10468 &[
10469 PktLineFrame::Data(b"want 1111111111111111111111111111111111111111\n".to_vec(),),
10470 PktLineFrame::Data(b"filter blob:none\n".to_vec()),
10471 PktLineFrame::Data(b"filter tree:0\n".to_vec()),
10472 PktLineFrame::Flush,
10473 ],
10474 )
10475 .is_err());
10476 assert!(encode_upload_pack_request(Some(&UploadPackRequest::default())).is_err());
10477 assert!(
10478 encode_upload_pack_request(Some(&UploadPackRequest {
10479 wants: vec![
10480 ObjectId::from_hex(
10481 ObjectFormat::Sha1,
10482 "1111111111111111111111111111111111111111",
10483 )
10484 .expect("test operation should succeed")
10485 ],
10486 deepen: Some(0),
10487 ..UploadPackRequest::default()
10488 }))
10489 .is_err()
10490 );
10491 }
10492
10493 #[test]
10494 fn upload_pack_shallow_update_parses_and_encodes_records() {
10495 let shallow = ObjectId::from_hex(
10496 ObjectFormat::Sha1,
10497 "1111111111111111111111111111111111111111",
10498 )
10499 .expect("test operation should succeed");
10500 let unshallow = ObjectId::from_hex(
10501 ObjectFormat::Sha1,
10502 "2222222222222222222222222222222222222222",
10503 )
10504 .expect("test operation should succeed");
10505 let frames = vec![
10506 PktLineFrame::Data(b"shallow 1111111111111111111111111111111111111111\n".to_vec()),
10507 PktLineFrame::Data(b"unshallow 2222222222222222222222222222222222222222\n".to_vec()),
10508 PktLineFrame::Flush,
10509 ];
10510 let entries = parse_upload_pack_shallow_update(ObjectFormat::Sha1, &frames)
10511 .expect("test operation should succeed");
10512 assert_eq!(
10513 entries,
10514 vec![
10515 ProtocolV2FetchShallowInfo::Shallow(shallow),
10516 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
10517 ]
10518 );
10519 assert_eq!(
10520 encode_upload_pack_shallow_update(&entries).expect("test operation should succeed"),
10521 frames
10522 );
10523 assert_eq!(
10524 parse_upload_pack_shallow_update(ObjectFormat::Sha1, &[PktLineFrame::Flush])
10525 .expect("test operation should succeed"),
10526 Vec::<ProtocolV2FetchShallowInfo>::new()
10527 );
10528 }
10529
10530 #[test]
10531 fn upload_pack_shallow_update_streams_round_trip() {
10532 let entries = vec![ProtocolV2FetchShallowInfo::Shallow(
10533 ObjectId::from_hex(
10534 ObjectFormat::Sha1,
10535 "1111111111111111111111111111111111111111",
10536 )
10537 .expect("test operation should succeed"),
10538 )];
10539 let mut encoded = Vec::new();
10540 write_upload_pack_shallow_update(&mut encoded, &entries)
10541 .expect("test operation should succeed");
10542 encoded.extend_from_slice(b"tail");
10543
10544 let mut input = encoded.as_slice();
10545 assert_eq!(
10546 read_upload_pack_shallow_update(ObjectFormat::Sha1, &mut input)
10547 .expect("test operation should succeed"),
10548 entries
10549 );
10550 assert_eq!(input, b"tail");
10551 }
10552
10553 #[test]
10554 fn upload_pack_shallow_update_rejects_malformed_records() {
10555 assert!(
10556 parse_upload_pack_shallow_update(
10557 ObjectFormat::Sha1,
10558 &[PktLineFrame::Data(
10559 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10560 )],
10561 )
10562 .is_err()
10563 );
10564 assert!(
10565 parse_upload_pack_shallow_update(
10566 ObjectFormat::Sha1,
10567 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10568 )
10569 .is_err()
10570 );
10571 assert!(
10572 parse_upload_pack_shallow_update(
10573 ObjectFormat::Sha1,
10574 &[
10575 PktLineFrame::Data(
10576 b"shallow 1111111111111111111111111111111111111111\n".to_vec(),
10577 ),
10578 PktLineFrame::Flush,
10579 PktLineFrame::Data(
10580 b"unshallow 2222222222222222222222222222222222222222\n".to_vec(),
10581 ),
10582 ],
10583 )
10584 .is_err()
10585 );
10586 assert!(
10587 parse_upload_pack_shallow_update(
10588 ObjectFormat::Sha1,
10589 &[
10590 PktLineFrame::Data(
10591 b"unsupported 1111111111111111111111111111111111111111\n".to_vec(),
10592 ),
10593 PktLineFrame::Flush,
10594 ],
10595 )
10596 .is_err()
10597 );
10598 }
10599
10600 #[test]
10601 fn upload_pack_negotiation_request_parses_flush_and_done_rounds() {
10602 let have = ObjectId::from_hex(
10603 ObjectFormat::Sha1,
10604 "1111111111111111111111111111111111111111",
10605 )
10606 .expect("test operation should succeed");
10607 let second_have = ObjectId::from_hex(
10608 ObjectFormat::Sha1,
10609 "2222222222222222222222222222222222222222",
10610 )
10611 .expect("test operation should succeed");
10612 let flush_round = vec![
10613 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10614 PktLineFrame::Data(b"have 2222222222222222222222222222222222222222\n".to_vec()),
10615 PktLineFrame::Flush,
10616 ];
10617 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &flush_round)
10618 .expect("test operation should succeed");
10619 assert_eq!(
10620 request,
10621 UploadPackNegotiationRequest {
10622 haves: vec![have, second_have],
10623 done: false,
10624 }
10625 );
10626 assert_eq!(
10627 encode_upload_pack_negotiation_request(&request)
10628 .expect("test operation should succeed"),
10629 flush_round
10630 );
10631
10632 let done_round = vec![
10633 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec()),
10634 PktLineFrame::Data(b"done\n".to_vec()),
10635 ];
10636 let request = parse_upload_pack_negotiation_request(ObjectFormat::Sha1, &done_round)
10637 .expect("test operation should succeed");
10638 assert_eq!(
10639 request,
10640 UploadPackNegotiationRequest {
10641 haves: vec![have],
10642 done: true,
10643 }
10644 );
10645 assert_eq!(
10646 encode_upload_pack_negotiation_request(&request)
10647 .expect("test operation should succeed"),
10648 done_round
10649 );
10650 }
10651
10652 #[test]
10653 fn upload_pack_negotiation_request_streams_round_trip() {
10654 let first = UploadPackNegotiationRequest {
10655 haves: vec![
10656 ObjectId::from_hex(
10657 ObjectFormat::Sha1,
10658 "1111111111111111111111111111111111111111",
10659 )
10660 .expect("test operation should succeed"),
10661 ],
10662 done: false,
10663 };
10664 let second = UploadPackNegotiationRequest {
10665 haves: Vec::new(),
10666 done: true,
10667 };
10668 let mut encoded = Vec::new();
10669 write_upload_pack_negotiation_request(&mut encoded, &first)
10670 .expect("test operation should succeed");
10671 write_upload_pack_negotiation_request(&mut encoded, &second)
10672 .expect("test operation should succeed");
10673 encoded.extend_from_slice(b"tail");
10674
10675 let mut input = encoded.as_slice();
10676 assert_eq!(
10677 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10678 .expect("test operation should succeed"),
10679 first
10680 );
10681 assert_eq!(
10682 read_upload_pack_negotiation_request(ObjectFormat::Sha1, &mut input)
10683 .expect("test operation should succeed"),
10684 second
10685 );
10686 assert_eq!(input, b"tail");
10687 }
10688
10689 #[test]
10690 fn upload_pack_negotiation_request_rejects_malformed_rounds() {
10691 assert!(
10692 parse_upload_pack_negotiation_request(
10693 ObjectFormat::Sha1,
10694 &[PktLineFrame::Data(
10695 b"have 1111111111111111111111111111111111111111\n".to_vec(),
10696 )],
10697 )
10698 .is_err()
10699 );
10700 assert!(
10701 parse_upload_pack_negotiation_request(
10702 ObjectFormat::Sha1,
10703 &[PktLineFrame::Data(
10704 b"want 1111111111111111111111111111111111111111\n".to_vec(),
10705 )],
10706 )
10707 .is_err()
10708 );
10709 assert!(parse_upload_pack_negotiation_request(
10710 ObjectFormat::Sha1,
10711 &[
10712 PktLineFrame::Data(b"done\n".to_vec()),
10713 PktLineFrame::Data(b"have 1111111111111111111111111111111111111111\n".to_vec(),),
10714 ],
10715 )
10716 .is_err());
10717 assert!(
10718 parse_upload_pack_negotiation_request(
10719 ObjectFormat::Sha1,
10720 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10721 )
10722 .is_err()
10723 );
10724 }
10725
10726 #[test]
10727 fn upload_pack_acknowledgments_parse_and_encode_statuses() {
10728 let oid = ObjectId::from_hex(
10729 ObjectFormat::Sha1,
10730 "1111111111111111111111111111111111111111",
10731 )
10732 .expect("test operation should succeed");
10733 assert_eq!(
10734 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"NAK\n")
10735 .expect("test operation should succeed"),
10736 UploadPackAcknowledgment::Nak
10737 );
10738 for (payload, status) in [
10739 (
10740 b"ACK 1111111111111111111111111111111111111111\n".as_slice(),
10741 None,
10742 ),
10743 (
10744 b"ACK 1111111111111111111111111111111111111111 continue\n".as_slice(),
10745 Some(UploadPackAckStatus::Continue),
10746 ),
10747 (
10748 b"ACK 1111111111111111111111111111111111111111 common\n".as_slice(),
10749 Some(UploadPackAckStatus::Common),
10750 ),
10751 (
10752 b"ACK 1111111111111111111111111111111111111111 ready\n".as_slice(),
10753 Some(UploadPackAckStatus::Ready),
10754 ),
10755 ] {
10756 let acknowledgment = parse_upload_pack_acknowledgment(ObjectFormat::Sha1, payload)
10757 .expect("test operation should succeed");
10758 assert_eq!(
10759 acknowledgment,
10760 UploadPackAcknowledgment::Ack { oid, status }
10761 );
10762 assert_eq!(
10763 encode_upload_pack_acknowledgment(&acknowledgment)
10764 .expect("test operation should succeed"),
10765 payload
10766 );
10767 }
10768 }
10769
10770 #[test]
10771 fn upload_pack_acknowledgments_stream_round_trip_and_reject_bad_lines() {
10772 let acknowledgment = UploadPackAcknowledgment::Ack {
10773 oid: ObjectId::from_hex(
10774 ObjectFormat::Sha1,
10775 "1111111111111111111111111111111111111111",
10776 )
10777 .expect("test operation should succeed"),
10778 status: Some(UploadPackAckStatus::Ready),
10779 };
10780 let mut encoded = Vec::new();
10781 write_upload_pack_acknowledgment(&mut encoded, &acknowledgment)
10782 .expect("test operation should succeed");
10783 encoded.extend_from_slice(b"tail");
10784
10785 let mut input = encoded.as_slice();
10786 assert_eq!(
10787 read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut input)
10788 .expect("test operation should succeed"),
10789 acknowledgment
10790 );
10791 assert_eq!(input, b"tail");
10792 assert!(parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ACK not-an-oid\n").is_err());
10793 assert!(
10794 parse_upload_pack_acknowledgment(
10795 ObjectFormat::Sha1,
10796 b"ACK 1111111111111111111111111111111111111111 unknown\n",
10797 )
10798 .is_err()
10799 );
10800 assert!(
10801 parse_upload_pack_acknowledgment(
10802 ObjectFormat::Sha1,
10803 b"ACK 1111111111111111111111111111111111111111 ready extra\n",
10804 )
10805 .is_err()
10806 );
10807 assert!(
10808 parse_upload_pack_acknowledgment(ObjectFormat::Sha1, b"ERR remote died\n").is_err()
10809 );
10810 assert!(read_upload_pack_acknowledgment(ObjectFormat::Sha1, &mut &b"0000"[..]).is_err());
10811 }
10812
10813 #[test]
10814 fn upload_pack_packfile_response_parses_acknowledgments_and_sideband() {
10815 let oid = ObjectId::from_hex(
10816 ObjectFormat::Sha1,
10817 "1111111111111111111111111111111111111111",
10818 )
10819 .expect("test operation should succeed");
10820 let frames = vec![
10821 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()),
10822 PktLineFrame::Data(b"NAK\n".to_vec()),
10823 PktLineFrame::Data(b"\x01PACK".to_vec()),
10824 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
10825 PktLineFrame::Data(b"\x01 bytes".to_vec()),
10826 PktLineFrame::Flush,
10827 ];
10828 let response = parse_upload_pack_packfile_response(ObjectFormat::Sha1, &frames)
10829 .expect("test operation should succeed");
10830 assert_eq!(
10831 response,
10832 UploadPackPackfileResponse {
10833 acknowledgments: vec![
10834 UploadPackAcknowledgment::Ack {
10835 oid,
10836 status: Some(UploadPackAckStatus::Common),
10837 },
10838 UploadPackAcknowledgment::Nak,
10839 ],
10840 sideband: vec![
10841 SideBandPacket {
10842 channel: SideBandChannel::Data,
10843 data: b"PACK".to_vec(),
10844 },
10845 SideBandPacket {
10846 channel: SideBandChannel::Progress,
10847 data: b"counting objects\n".to_vec(),
10848 },
10849 SideBandPacket {
10850 channel: SideBandChannel::Data,
10851 data: b" bytes".to_vec(),
10852 },
10853 ],
10854 }
10855 );
10856 assert_eq!(
10857 demux_upload_pack_packfile_response(&response).expect("test operation should succeed"),
10858 SideBandDemux {
10859 data: b"PACK bytes".to_vec(),
10860 progress: vec![b"counting objects\n".to_vec()],
10861 }
10862 );
10863 assert_eq!(
10864 encode_upload_pack_packfile_response(&response).expect("test operation should succeed"),
10865 frames
10866 );
10867 }
10868
10869 #[test]
10870 fn upload_pack_packfile_response_streams_round_trip() {
10871 let response = UploadPackPackfileResponse {
10872 acknowledgments: vec![UploadPackAcknowledgment::Nak],
10873 sideband: vec![SideBandPacket {
10874 channel: SideBandChannel::Data,
10875 data: b"PACK".to_vec(),
10876 }],
10877 };
10878 let mut encoded = Vec::new();
10879 write_upload_pack_packfile_response(&mut encoded, &response)
10880 .expect("test operation should succeed");
10881 encoded.extend_from_slice(b"tail");
10882
10883 let mut input = encoded.as_slice();
10884 assert_eq!(
10885 read_upload_pack_packfile_response(ObjectFormat::Sha1, &mut input)
10886 .expect("test operation should succeed"),
10887 response
10888 );
10889 assert_eq!(input, b"tail");
10890 }
10891
10892 #[test]
10893 fn upload_pack_packfile_response_rejects_malformed_streams() {
10894 assert!(
10895 parse_upload_pack_packfile_response(
10896 ObjectFormat::Sha1,
10897 &[PktLineFrame::Data(b"NAK\n".to_vec())],
10898 )
10899 .is_err()
10900 );
10901 assert!(
10902 parse_upload_pack_packfile_response(
10903 ObjectFormat::Sha1,
10904 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
10905 )
10906 .is_err()
10907 );
10908 assert!(
10909 parse_upload_pack_packfile_response(
10910 ObjectFormat::Sha1,
10911 &[
10912 PktLineFrame::Data(b"\x01PACK".to_vec()),
10913 PktLineFrame::Data(
10914 b"ACK 1111111111111111111111111111111111111111 common\n".to_vec()
10915 ),
10916 PktLineFrame::Flush,
10917 ],
10918 )
10919 .is_err()
10920 );
10921 assert!(
10922 parse_upload_pack_packfile_response(
10923 ObjectFormat::Sha1,
10924 &[
10925 PktLineFrame::Data(b"NAK\n".to_vec()),
10926 PktLineFrame::Flush,
10927 PktLineFrame::Data(b"\x01PACK".to_vec()),
10928 ],
10929 )
10930 .is_err()
10931 );
10932 assert!(
10933 parse_upload_pack_packfile_response(
10934 ObjectFormat::Sha1,
10935 &[
10936 PktLineFrame::Data(b"NAK\n".to_vec()),
10937 PktLineFrame::Data(b"\x04bad".to_vec()),
10938 PktLineFrame::Flush,
10939 ],
10940 )
10941 .is_err()
10942 );
10943 }
10944
10945 #[test]
10946 fn upload_pack_raw_packfile_response_parses_acknowledgments_and_raw_pack() {
10947 let oid = ObjectId::from_hex(
10948 ObjectFormat::Sha1,
10949 "1111111111111111111111111111111111111111",
10950 )
10951 .expect("test operation should succeed");
10952 let response = UploadPackRawPackfileResponse {
10953 acknowledgments: vec![
10954 UploadPackAcknowledgment::Ack {
10955 oid,
10956 status: Some(UploadPackAckStatus::Common),
10957 },
10958 UploadPackAcknowledgment::Nak,
10959 ],
10960 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
10961 };
10962 let encoded = encode_upload_pack_raw_packfile_response(&response)
10963 .expect("test operation should succeed");
10964 assert_eq!(
10965 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &encoded)
10966 .expect("test operation should succeed"),
10967 response
10968 );
10969 }
10970
10971 #[test]
10972 fn upload_pack_raw_packfile_response_streams_round_trip() {
10973 let response = UploadPackRawPackfileResponse {
10974 acknowledgments: vec![UploadPackAcknowledgment::Nak],
10975 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
10976 };
10977 let mut encoded = Vec::new();
10978 write_upload_pack_raw_packfile_response(&mut encoded, &response)
10979 .expect("test operation should succeed");
10980 assert_eq!(
10981 encoded,
10982 encode_upload_pack_raw_packfile_response(&response)
10983 .expect("test operation should succeed")
10984 );
10985
10986 let mut input = encoded.as_slice();
10987 assert_eq!(
10988 read_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &mut input)
10989 .expect("test operation should succeed"),
10990 response
10991 );
10992 assert!(input.is_empty());
10993 }
10994
10995 #[test]
10996 fn upload_pack_raw_packfile_response_rejects_malformed_streams() {
10997 let ack = PktLineFrame::data(b"NAK\n".to_vec())
10998 .expect("test operation should succeed")
10999 .try_encode()
11000 .expect("test operation should succeed");
11001 let bad_ack = PktLineFrame::data(b"ACK not-an-oid\n".to_vec())
11002 .expect("test operation should succeed")
11003 .try_encode()
11004 .expect("test operation should succeed");
11005 let non_ack =
11006 PktLineFrame::data(b"have 1111111111111111111111111111111111111111\n".to_vec())
11007 .expect("test operation should succeed")
11008 .try_encode()
11009 .expect("test operation should succeed");
11010 let mut garbage_after_ack = ack.clone();
11011 garbage_after_ack.extend_from_slice(b"garbage");
11012
11013 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"").is_err());
11014 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &ack).is_err());
11015 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &bad_ack).is_err());
11016 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, b"0000PACK").is_err());
11017 assert!(parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &non_ack).is_err());
11018 assert!(
11019 parse_upload_pack_raw_packfile_response(ObjectFormat::Sha1, &garbage_after_ack)
11020 .is_err()
11021 );
11022 assert!(
11023 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11024 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11025 packfile: Vec::new(),
11026 })
11027 .is_err()
11028 );
11029 assert!(
11030 encode_upload_pack_raw_packfile_response(&UploadPackRawPackfileResponse {
11031 acknowledgments: Vec::new(),
11032 packfile: b"not-a-pack".to_vec(),
11033 })
11034 .is_err()
11035 );
11036 }
11037
11038 #[test]
11039 fn upload_pack_request_encodes_deepen_request() {
11040 let want = ObjectId::from_hex(
11045 ObjectFormat::Sha1,
11046 "1111111111111111111111111111111111111111",
11047 )
11048 .expect("test operation should succeed");
11049 let boundary = ObjectId::from_hex(
11050 ObjectFormat::Sha1,
11051 "2222222222222222222222222222222222222222",
11052 )
11053 .expect("test operation should succeed");
11054 let request = UploadPackRequest {
11055 wants: vec![want],
11056 capabilities: vec![Capability {
11057 name: "shallow".into(),
11058 value: None,
11059 }],
11060 shallow: vec![boundary],
11061 deepen: Some(1),
11062 ..UploadPackRequest::default()
11063 };
11064 let mut encoded = Vec::new();
11065 write_upload_pack_request(&mut encoded, Some(&request))
11066 .expect("test operation should succeed");
11067 let mut expected = Vec::new();
11068 expected.extend_from_slice(b"003awant 1111111111111111111111111111111111111111 shallow\n");
11069 expected.extend_from_slice(b"0035shallow 2222222222222222222222222222222222222222\n");
11070 expected.extend_from_slice(b"000ddeepen 1\n");
11071 expected.extend_from_slice(b"0000");
11072 assert_eq!(encoded, expected);
11073 }
11074
11075 #[test]
11076 fn upload_pack_shallow_info_response_parses_shallow_unshallow_and_pack() {
11077 let shallow = ObjectId::from_hex(
11081 ObjectFormat::Sha1,
11082 "1111111111111111111111111111111111111111",
11083 )
11084 .expect("test operation should succeed");
11085 let unshallow = ObjectId::from_hex(
11086 ObjectFormat::Sha1,
11087 "2222222222222222222222222222222222222222",
11088 )
11089 .expect("test operation should succeed");
11090 let mut input = Vec::new();
11091 input.extend_from_slice(b"0035shallow 1111111111111111111111111111111111111111\n");
11092 input.extend_from_slice(b"0037unshallow 2222222222222222222222222222222222222222\n");
11093 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11095 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11096
11097 let (entries, response) =
11098 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11099 .expect("test operation should succeed");
11100 assert_eq!(
11101 entries,
11102 vec![
11103 ProtocolV2FetchShallowInfo::Shallow(shallow),
11104 ProtocolV2FetchShallowInfo::Unshallow(unshallow),
11105 ]
11106 );
11107 assert_eq!(
11108 response,
11109 UploadPackRawPackfileResponse {
11110 acknowledgments: vec![UploadPackAcknowledgment::Nak],
11111 packfile: b"PACK\x00\x00\x00\x02raw-bytes".to_vec(),
11112 }
11113 );
11114
11115 let mut stream = input.as_slice();
11117 let (read_entries, read_response) =
11118 read_upload_pack_shallow_info_and_raw_packfile_response(
11119 ObjectFormat::Sha1,
11120 &mut stream,
11121 )
11122 .expect("test operation should succeed");
11123 assert_eq!(read_entries, entries);
11124 assert_eq!(read_response, response);
11125 }
11126
11127 #[test]
11128 fn upload_pack_shallow_info_response_handles_empty_shallow_section() {
11129 let mut input = Vec::new();
11132 input.extend_from_slice(b"0000"); input.extend_from_slice(b"0008NAK\n");
11134 input.extend_from_slice(b"PACK\x00\x00\x00\x02raw-bytes");
11135
11136 let (entries, response) =
11137 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &input)
11138 .expect("test operation should succeed");
11139 assert!(entries.is_empty());
11140 assert_eq!(
11141 response.acknowledgments,
11142 vec![UploadPackAcknowledgment::Nak]
11143 );
11144 assert!(response.packfile.starts_with(b"PACK"));
11145 }
11146
11147 #[test]
11148 fn upload_pack_shallow_info_response_rejects_malformed_sections() {
11149 let truncated = b"0035shallow 1111111111111111111111111111111111111111\n".to_vec();
11151 assert!(
11152 parse_upload_pack_shallow_info_and_raw_packfile_response(
11153 ObjectFormat::Sha1,
11154 &truncated
11155 )
11156 .is_err()
11157 );
11158 let mut delimiter_section = Vec::new();
11160 delimiter_section.extend_from_slice(b"0001"); assert!(
11162 parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &delimiter_section).is_err()
11163 );
11164 let mut bad_line = Vec::new();
11166 bad_line.extend_from_slice(b"0008NAK\n");
11167 assert!(parse_upload_pack_shallow_info_section(ObjectFormat::Sha1, &bad_line).is_err());
11168 let mut no_pack = Vec::new();
11170 no_pack.extend_from_slice(b"0000"); no_pack.extend_from_slice(b"0008NAK\n");
11172 assert!(
11173 parse_upload_pack_shallow_info_and_raw_packfile_response(ObjectFormat::Sha1, &no_pack)
11174 .is_err()
11175 );
11176 }
11177
11178 #[test]
11179 fn receive_pack_request_parses_and_encodes_commands() {
11180 let old_id = ObjectId::from_hex(
11181 ObjectFormat::Sha1,
11182 "1111111111111111111111111111111111111111",
11183 )
11184 .expect("test operation should succeed");
11185 let new_id = ObjectId::from_hex(
11186 ObjectFormat::Sha1,
11187 "2222222222222222222222222222222222222222",
11188 )
11189 .expect("test operation should succeed");
11190 let delete_old_id = ObjectId::from_hex(
11191 ObjectFormat::Sha1,
11192 "3333333333333333333333333333333333333333",
11193 )
11194 .expect("test operation should succeed");
11195 let zero = ObjectId::from_hex(
11196 ObjectFormat::Sha1,
11197 "0000000000000000000000000000000000000000",
11198 )
11199 .expect("test operation should succeed");
11200 let shallow = ObjectId::from_hex(
11201 ObjectFormat::Sha1,
11202 "4444444444444444444444444444444444444444",
11203 )
11204 .expect("test operation should succeed");
11205 let frames = vec![
11206 PktLineFrame::Data(b"shallow 4444444444444444444444444444444444444444\n".to_vec()),
11207 PktLineFrame::Data(
11208 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status side-band-64k agent=git/2.54.0\n"
11209 .to_vec(),
11210 ),
11211 PktLineFrame::Data(
11212 b"3333333333333333333333333333333333333333 0000000000000000000000000000000000000000 refs/heads/old\n"
11213 .to_vec(),
11214 ),
11215 PktLineFrame::Flush,
11216 ];
11217 let request = parse_receive_pack_request(ObjectFormat::Sha1, &frames)
11218 .expect("test operation should succeed");
11219 assert_eq!(
11220 request,
11221 ReceivePackRequest {
11222 shallow: vec![shallow],
11223 commands: vec![
11224 ReceivePackCommand {
11225 old_id,
11226 new_id,
11227 name: "refs/heads/main".into(),
11228 },
11229 ReceivePackCommand {
11230 old_id: delete_old_id,
11231 new_id: zero,
11232 name: "refs/heads/old".into(),
11233 },
11234 ],
11235 capabilities: vec![
11236 Capability {
11237 name: "report-status".into(),
11238 value: None,
11239 },
11240 Capability {
11241 name: "side-band-64k".into(),
11242 value: None,
11243 },
11244 Capability {
11245 name: "agent".into(),
11246 value: Some("git/2.54.0".into()),
11247 },
11248 ],
11249 }
11250 );
11251 assert_eq!(
11252 encode_receive_pack_request(&request).expect("test operation should succeed"),
11253 frames
11254 );
11255 assert_eq!(
11256 parse_receive_pack_request(ObjectFormat::Sha1, &[PktLineFrame::Flush])
11257 .expect("test operation should succeed"),
11258 ReceivePackRequest::default()
11259 );
11260 }
11261
11262 #[test]
11263 fn receive_pack_request_streams_round_trip() {
11264 let request = ReceivePackRequest {
11265 commands: vec![ReceivePackCommand {
11266 old_id: ObjectId::from_hex(
11267 ObjectFormat::Sha1,
11268 "0000000000000000000000000000000000000000",
11269 )
11270 .expect("test operation should succeed"),
11271 new_id: ObjectId::from_hex(
11272 ObjectFormat::Sha1,
11273 "1111111111111111111111111111111111111111",
11274 )
11275 .expect("test operation should succeed"),
11276 name: "refs/heads/main".into(),
11277 }],
11278 capabilities: vec![Capability {
11279 name: "report-status".into(),
11280 value: None,
11281 }],
11282 ..ReceivePackRequest::default()
11283 };
11284 let mut encoded = Vec::new();
11285 write_receive_pack_request(&mut encoded, &request).expect("test operation should succeed");
11286 encoded.extend_from_slice(b"PACK");
11287
11288 let mut input = encoded.as_slice();
11289 assert_eq!(
11290 read_receive_pack_request(ObjectFormat::Sha1, &mut input)
11291 .expect("test operation should succeed"),
11292 request
11293 );
11294 assert_eq!(input, b"PACK");
11295 }
11296
11297 #[test]
11298 fn receive_pack_request_rejects_malformed_commands() {
11299 assert!(
11300 parse_receive_pack_request(
11301 ObjectFormat::Sha1,
11302 &[PktLineFrame::Data(
11303 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11304 .to_vec(),
11305 )],
11306 )
11307 .is_err()
11308 );
11309 assert!(
11310 parse_receive_pack_request(
11311 ObjectFormat::Sha1,
11312 &[
11313 PktLineFrame::Data(
11314 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\n"
11315 .to_vec(),
11316 ),
11317 PktLineFrame::Data(
11318 b"shallow 3333333333333333333333333333333333333333\n".to_vec(),
11319 ),
11320 PktLineFrame::Flush,
11321 ],
11322 )
11323 .is_err()
11324 );
11325 assert!(
11326 parse_receive_pack_request(
11327 ObjectFormat::Sha1,
11328 &[
11329 PktLineFrame::Data(
11330 b"1111111111111111111111111111111111111111 2222222222222222222222222222222222222222 refs/heads/main\0report-status\n"
11331 .to_vec(),
11332 ),
11333 PktLineFrame::Data(
11334 b"3333333333333333333333333333333333333333 4444444444444444444444444444444444444444 refs/heads/next\0side-band-64k\n"
11335 .to_vec(),
11336 ),
11337 PktLineFrame::Flush,
11338 ],
11339 )
11340 .is_err()
11341 );
11342 assert!(
11343 parse_receive_pack_request(
11344 ObjectFormat::Sha1,
11345 &[
11346 PktLineFrame::Data(
11347 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
11348 ),
11349 PktLineFrame::Flush,
11350 ],
11351 )
11352 .is_err()
11353 );
11354 assert!(
11355 encode_receive_pack_request(&ReceivePackRequest {
11356 shallow: vec![
11357 ObjectId::from_hex(
11358 ObjectFormat::Sha1,
11359 "1111111111111111111111111111111111111111",
11360 )
11361 .expect("test operation should succeed")
11362 ],
11363 ..ReceivePackRequest::default()
11364 })
11365 .is_err()
11366 );
11367 assert!(
11368 encode_receive_pack_request(&ReceivePackRequest {
11369 commands: vec![ReceivePackCommand {
11370 old_id: ObjectId::from_hex(
11371 ObjectFormat::Sha1,
11372 "1111111111111111111111111111111111111111",
11373 )
11374 .expect("test operation should succeed"),
11375 new_id: ObjectId::from_hex(
11376 ObjectFormat::Sha1,
11377 "2222222222222222222222222222222222222222",
11378 )
11379 .expect("test operation should succeed"),
11380 name: "bad ref".into(),
11381 }],
11382 ..ReceivePackRequest::default()
11383 })
11384 .is_err()
11385 );
11386 }
11387
11388 #[test]
11389 fn receive_pack_features_parse_encode_and_validate_push_request() {
11390 let capabilities = vec![
11391 Capability {
11392 name: "report-status".into(),
11393 value: None,
11394 },
11395 Capability {
11396 name: "report-status-v2".into(),
11397 value: None,
11398 },
11399 Capability {
11400 name: "delete-refs".into(),
11401 value: None,
11402 },
11403 Capability {
11404 name: "ofs-delta".into(),
11405 value: None,
11406 },
11407 Capability {
11408 name: "atomic".into(),
11409 value: None,
11410 },
11411 Capability {
11412 name: "push-options".into(),
11413 value: None,
11414 },
11415 Capability {
11416 name: "side-band-64k".into(),
11417 value: None,
11418 },
11419 Capability {
11420 name: "quiet".into(),
11421 value: None,
11422 },
11423 Capability {
11424 name: "no-thin".into(),
11425 value: None,
11426 },
11427 Capability {
11428 name: "agent".into(),
11429 value: Some("git/2.54.0".into()),
11430 },
11431 Capability {
11432 name: "object-format".into(),
11433 value: Some("sha256".into()),
11434 },
11435 Capability {
11436 name: "custom".into(),
11437 value: Some("value".into()),
11438 },
11439 ];
11440 let features =
11441 parse_receive_pack_features(&capabilities).expect("test operation should succeed");
11442 assert_eq!(
11443 features,
11444 ReceivePackFeatures {
11445 report_status: true,
11446 report_status_v2: true,
11447 delete_refs: true,
11448 ofs_delta: true,
11449 atomic: true,
11450 push_options: true,
11451 side_band_64k: true,
11452 quiet: true,
11453 no_thin: true,
11454 agent: Some("git/2.54.0".into()),
11455 object_format: Some(ObjectFormat::Sha256),
11456 unknown: vec![Capability {
11457 name: "custom".into(),
11458 value: Some("value".into()),
11459 }],
11460 }
11461 );
11462 assert_eq!(
11463 encode_receive_pack_features(&features).expect("test operation should succeed"),
11464 capabilities
11465 );
11466
11467 let request = ReceivePackPushRequest {
11468 commands: ReceivePackRequest {
11469 commands: vec![ReceivePackCommand {
11470 old_id: ObjectId::from_hex(
11471 ObjectFormat::Sha1,
11472 "1111111111111111111111111111111111111111",
11473 )
11474 .expect("test operation should succeed"),
11475 new_id: ObjectId::from_hex(
11476 ObjectFormat::Sha1,
11477 "2222222222222222222222222222222222222222",
11478 )
11479 .expect("test operation should succeed"),
11480 name: "refs/heads/main".into(),
11481 }],
11482 capabilities: vec![
11483 Capability {
11484 name: "report-status".into(),
11485 value: None,
11486 },
11487 Capability {
11488 name: "ofs-delta".into(),
11489 value: None,
11490 },
11491 Capability {
11492 name: "push-options".into(),
11493 value: None,
11494 },
11495 Capability {
11496 name: "side-band-64k".into(),
11497 value: None,
11498 },
11499 Capability {
11500 name: "agent".into(),
11501 value: Some("sley".into()),
11502 },
11503 ],
11504 ..ReceivePackRequest::default()
11505 },
11506 push_options: Some(vec!["ci.skip".into()]),
11507 packfile: b"PACKpayload".to_vec(),
11508 };
11509 validate_receive_pack_push_request_features(&features, &request)
11510 .expect("test operation should succeed");
11511 }
11512
11513 #[test]
11514 fn receive_pack_features_reject_invalid_push_requests() {
11515 let old_id = ObjectId::from_hex(
11516 ObjectFormat::Sha1,
11517 "1111111111111111111111111111111111111111",
11518 )
11519 .expect("test operation should succeed");
11520 let new_id = ObjectId::from_hex(
11521 ObjectFormat::Sha1,
11522 "2222222222222222222222222222222222222222",
11523 )
11524 .expect("test operation should succeed");
11525 let zero = ObjectId::from_hex(
11526 ObjectFormat::Sha1,
11527 "0000000000000000000000000000000000000000",
11528 )
11529 .expect("test operation should succeed");
11530 let features = ReceivePackFeatures {
11531 report_status: true,
11532 push_options: true,
11533 ..ReceivePackFeatures::default()
11534 };
11535 let update = ReceivePackCommand {
11536 old_id: old_id.clone(),
11537 new_id: new_id.clone(),
11538 name: "refs/heads/main".into(),
11539 };
11540
11541 assert!(
11542 validate_receive_pack_push_request_features(
11543 &features,
11544 &ReceivePackPushRequest {
11545 commands: ReceivePackRequest {
11546 commands: vec![update.clone()],
11547 capabilities: vec![Capability {
11548 name: "push-options".into(),
11549 value: None,
11550 }],
11551 ..ReceivePackRequest::default()
11552 },
11553 push_options: None,
11554 packfile: b"PACKpayload".to_vec(),
11555 },
11556 )
11557 .is_err()
11558 );
11559 assert!(
11560 validate_receive_pack_push_request_features(
11561 &features,
11562 &ReceivePackPushRequest {
11563 commands: ReceivePackRequest {
11564 commands: vec![update.clone()],
11565 ..ReceivePackRequest::default()
11566 },
11567 push_options: Some(Vec::new()),
11568 packfile: b"PACKpayload".to_vec(),
11569 },
11570 )
11571 .is_err()
11572 );
11573 assert!(
11574 validate_receive_pack_push_request_features(
11575 &features,
11576 &ReceivePackPushRequest {
11577 commands: ReceivePackRequest {
11578 commands: vec![ReceivePackCommand {
11579 old_id: old_id.clone(),
11580 new_id: zero.clone(),
11581 name: "refs/heads/main".into(),
11582 }],
11583 ..ReceivePackRequest::default()
11584 },
11585 push_options: None,
11586 packfile: Vec::new(),
11587 },
11588 )
11589 .is_err()
11590 );
11591 assert!(
11592 validate_receive_pack_push_request_features(
11593 &features,
11594 &ReceivePackPushRequest {
11595 commands: ReceivePackRequest {
11596 commands: vec![update.clone()],
11597 ..ReceivePackRequest::default()
11598 },
11599 push_options: None,
11600 packfile: Vec::new(),
11601 },
11602 )
11603 .is_err()
11604 );
11605 assert!(
11606 validate_receive_pack_push_request_features(
11607 &ReceivePackFeatures {
11608 delete_refs: true,
11609 ..ReceivePackFeatures::default()
11610 },
11611 &ReceivePackPushRequest {
11612 commands: ReceivePackRequest {
11613 commands: vec![ReceivePackCommand {
11614 old_id,
11615 new_id: zero,
11616 name: "refs/heads/main".into(),
11617 }],
11618 ..ReceivePackRequest::default()
11619 },
11620 push_options: None,
11621 packfile: b"PACKpayload".to_vec(),
11622 },
11623 )
11624 .is_err()
11625 );
11626 assert!(
11627 validate_receive_pack_push_request_features(
11628 &features,
11629 &ReceivePackPushRequest {
11630 commands: ReceivePackRequest {
11631 commands: vec![update],
11632 capabilities: vec![Capability {
11633 name: "atomic".into(),
11634 value: None,
11635 }],
11636 ..ReceivePackRequest::default()
11637 },
11638 push_options: None,
11639 packfile: b"PACKpayload".to_vec(),
11640 },
11641 )
11642 .is_err()
11643 );
11644
11645 assert!(
11646 parse_receive_pack_features(&[
11647 Capability {
11648 name: "push-options".into(),
11649 value: None,
11650 },
11651 Capability {
11652 name: "push-options".into(),
11653 value: None,
11654 },
11655 ])
11656 .is_err()
11657 );
11658 assert!(
11659 encode_receive_pack_features(&ReceivePackFeatures {
11660 unknown: vec![Capability {
11661 name: "atomic".into(),
11662 value: None,
11663 }],
11664 ..ReceivePackFeatures::default()
11665 })
11666 .is_err()
11667 );
11668 }
11669
11670 #[test]
11671 fn receive_pack_apply_helper_installs_pack_verifies_objects_and_reports_ok() {
11672 let old_id = ObjectId::from_hex(
11673 ObjectFormat::Sha1,
11674 "1111111111111111111111111111111111111111",
11675 )
11676 .expect("test operation should succeed");
11677 let new_id = ObjectId::from_hex(
11678 ObjectFormat::Sha1,
11679 "2222222222222222222222222222222222222222",
11680 )
11681 .expect("test operation should succeed");
11682 let request = ReceivePackPushRequest {
11683 commands: ReceivePackRequest {
11684 commands: vec![ReceivePackCommand {
11685 old_id: old_id.clone(),
11686 new_id: new_id.clone(),
11687 name: "refs/heads/main".into(),
11688 }],
11689 ..ReceivePackRequest::default()
11690 },
11691 packfile: b"PACKpayload".to_vec(),
11692 ..ReceivePackPushRequest::default()
11693 };
11694 let installed = std::cell::Cell::new(false);
11695 let applied = std::cell::RefCell::new(Vec::new());
11696
11697 let report = apply_receive_pack_push_request(
11698 &ReceivePackFeatures::default(),
11699 &request,
11700 |_| unreachable!("update stale-old checks belong to the ref transaction callback"),
11701 |packfile| {
11702 assert_eq!(packfile, b"PACKpayload");
11703 installed.set(true);
11704 Ok(())
11705 },
11706 |oid| Ok(oid == &new_id),
11707 |commands| {
11708 applied.borrow_mut().extend_from_slice(commands);
11709 Ok(())
11710 },
11711 |_| unreachable!("no delete command should be applied"),
11712 )
11713 .expect("test operation should succeed");
11714
11715 assert!(installed.get());
11716 assert_eq!(applied.into_inner(), request.commands.commands);
11717 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11718 assert_eq!(
11719 report.commands,
11720 vec![ReceivePackCommandStatus::Ok {
11721 name: "refs/heads/main".into(),
11722 }]
11723 );
11724 }
11725
11726 #[test]
11727 fn receive_pack_apply_helper_preserves_delete_only_and_stale_delete_rules() {
11728 let old_id = ObjectId::from_hex(
11729 ObjectFormat::Sha1,
11730 "1111111111111111111111111111111111111111",
11731 )
11732 .expect("test operation should succeed");
11733 let other_id = ObjectId::from_hex(
11734 ObjectFormat::Sha1,
11735 "2222222222222222222222222222222222222222",
11736 )
11737 .expect("test operation should succeed");
11738 let zero = zero_object_id(ObjectFormat::Sha1).expect("test operation should succeed");
11739 let request = ReceivePackPushRequest {
11740 commands: ReceivePackRequest {
11741 commands: vec![ReceivePackCommand {
11742 old_id: old_id.clone(),
11743 new_id: zero,
11744 name: "refs/heads/main".into(),
11745 }],
11746 ..ReceivePackRequest::default()
11747 },
11748 ..ReceivePackPushRequest::default()
11749 };
11750 let features = ReceivePackFeatures {
11751 delete_refs: true,
11752 ..ReceivePackFeatures::default()
11753 };
11754 let installed = std::cell::Cell::new(false);
11755 let deleted = std::cell::RefCell::new(Vec::new());
11756
11757 let report = apply_receive_pack_push_request(
11758 &features,
11759 &request,
11760 |_| Ok(Some(old_id.clone())),
11761 |_| {
11762 installed.set(true);
11763 Ok(())
11764 },
11765 |_| Ok(false),
11766 |_| unreachable!("delete-only request should not apply updates"),
11767 |command| {
11768 deleted.borrow_mut().push(command.name.clone());
11769 Ok(())
11770 },
11771 )
11772 .expect("test operation should succeed");
11773
11774 assert!(!installed.get());
11775 assert_eq!(deleted.into_inner(), vec!["refs/heads/main"]);
11776 assert_eq!(report.unpack, ReceivePackUnpackStatus::Ok);
11777 assert!(
11778 apply_receive_pack_push_request(
11779 &features,
11780 &request,
11781 |_| Ok(Some(other_id.clone())),
11782 |_| Ok(()),
11783 |_| Ok(false),
11784 |_| Ok(()),
11785 |_| Ok(()),
11786 )
11787 .is_err()
11788 );
11789 }
11790
11791 #[test]
11792 fn receive_pack_push_request_parses_commands_options_and_packfile() {
11793 let command = ReceivePackCommand {
11794 old_id: ObjectId::from_hex(
11795 ObjectFormat::Sha1,
11796 "1111111111111111111111111111111111111111",
11797 )
11798 .expect("test operation should succeed"),
11799 new_id: ObjectId::from_hex(
11800 ObjectFormat::Sha1,
11801 "2222222222222222222222222222222222222222",
11802 )
11803 .expect("test operation should succeed"),
11804 name: "refs/heads/main".into(),
11805 };
11806 let expected = ReceivePackPushRequest {
11807 commands: ReceivePackRequest {
11808 commands: vec![command],
11809 capabilities: vec![
11810 Capability {
11811 name: "report-status".into(),
11812 value: None,
11813 },
11814 Capability {
11815 name: "push-options".into(),
11816 value: None,
11817 },
11818 ],
11819 ..ReceivePackRequest::default()
11820 },
11821 push_options: Some(vec!["ci.skip".into(), "deploy=staging".into()]),
11822 packfile: b"PACK\x00\x00\x00\x02payload".to_vec(),
11823 };
11824 let encoded =
11825 encode_receive_pack_push_request(&expected).expect("test operation should succeed");
11826
11827 assert_eq!(
11828 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true)
11829 .expect("test operation should succeed"),
11830 expected
11831 );
11832 }
11833
11834 #[test]
11835 fn receive_pack_push_request_preserves_packfile_without_push_options() {
11836 let request = ReceivePackPushRequest {
11837 commands: ReceivePackRequest {
11838 commands: vec![ReceivePackCommand {
11839 old_id: ObjectId::from_hex(
11840 ObjectFormat::Sha1,
11841 "1111111111111111111111111111111111111111",
11842 )
11843 .expect("test operation should succeed"),
11844 new_id: ObjectId::from_hex(
11845 ObjectFormat::Sha1,
11846 "2222222222222222222222222222222222222222",
11847 )
11848 .expect("test operation should succeed"),
11849 name: "refs/heads/main".into(),
11850 }],
11851 ..ReceivePackRequest::default()
11852 },
11853 push_options: None,
11854 packfile: b"0000PACK-like bytes stay raw".to_vec(),
11855 };
11856 let encoded =
11857 encode_receive_pack_push_request(&request).expect("test operation should succeed");
11858
11859 assert_eq!(
11860 parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, false)
11861 .expect("test operation should succeed"),
11862 request
11863 );
11864 }
11865
11866 #[test]
11867 fn receive_pack_push_request_streams_round_trip() {
11868 let request = ReceivePackPushRequest {
11869 commands: ReceivePackRequest {
11870 commands: vec![ReceivePackCommand {
11871 old_id: ObjectId::from_hex(
11872 ObjectFormat::Sha1,
11873 "1111111111111111111111111111111111111111",
11874 )
11875 .expect("test operation should succeed"),
11876 new_id: ObjectId::from_hex(
11877 ObjectFormat::Sha1,
11878 "2222222222222222222222222222222222222222",
11879 )
11880 .expect("test operation should succeed"),
11881 name: "refs/heads/main".into(),
11882 }],
11883 capabilities: vec![Capability {
11884 name: "push-options".into(),
11885 value: None,
11886 }],
11887 ..ReceivePackRequest::default()
11888 },
11889 push_options: Some(Vec::new()),
11890 packfile: b"PACKpayload".to_vec(),
11891 };
11892 let mut encoded = Vec::new();
11893 write_receive_pack_push_request(&mut encoded, &request)
11894 .expect("test operation should succeed");
11895
11896 assert_eq!(
11897 read_receive_pack_push_request(ObjectFormat::Sha1, &mut encoded.as_slice(), true)
11898 .expect("test operation should succeed"),
11899 request
11900 );
11901 }
11902
11903 #[test]
11904 fn receive_pack_push_request_rejects_malformed_sections() {
11905 assert!(
11906 parse_receive_pack_push_request(
11907 ObjectFormat::Sha1,
11908 b"0014not-a-command\n0000PACK",
11909 false,
11910 )
11911 .is_err()
11912 );
11913
11914 let request = ReceivePackPushRequest {
11915 commands: ReceivePackRequest {
11916 commands: vec![ReceivePackCommand {
11917 old_id: ObjectId::from_hex(
11918 ObjectFormat::Sha1,
11919 "1111111111111111111111111111111111111111",
11920 )
11921 .expect("test operation should succeed"),
11922 new_id: ObjectId::from_hex(
11923 ObjectFormat::Sha1,
11924 "2222222222222222222222222222222222222222",
11925 )
11926 .expect("test operation should succeed"),
11927 name: "refs/heads/main".into(),
11928 }],
11929 ..ReceivePackRequest::default()
11930 },
11931 push_options: None,
11932 packfile: b"PACKpayload".to_vec(),
11933 };
11934 let encoded =
11935 encode_receive_pack_push_request(&request).expect("test operation should succeed");
11936 assert!(parse_receive_pack_push_request(ObjectFormat::Sha1, &encoded, true).is_err());
11937
11938 assert!(
11939 encode_receive_pack_push_request(&ReceivePackPushRequest {
11940 commands: ReceivePackRequest {
11941 shallow: vec![
11942 ObjectId::from_hex(
11943 ObjectFormat::Sha1,
11944 "1111111111111111111111111111111111111111",
11945 )
11946 .expect("test operation should succeed")
11947 ],
11948 ..ReceivePackRequest::default()
11949 },
11950 push_options: None,
11951 packfile: Vec::new(),
11952 })
11953 .is_err()
11954 );
11955 }
11956
11957 #[test]
11958 fn receive_pack_report_status_parses_and_encodes_status_lines() {
11959 let frames = vec![
11960 PktLineFrame::Data(b"unpack ok\n".to_vec()),
11961 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
11962 PktLineFrame::Data(b"ng refs/heads/old non-fast-forward\n".to_vec()),
11963 PktLineFrame::Flush,
11964 ];
11965 let report =
11966 parse_receive_pack_report_status(&frames).expect("test operation should succeed");
11967 assert_eq!(
11968 report,
11969 ReceivePackReportStatus {
11970 unpack: ReceivePackUnpackStatus::Ok,
11971 commands: vec![
11972 ReceivePackCommandStatus::Ok {
11973 name: "refs/heads/main".into(),
11974 },
11975 ReceivePackCommandStatus::Ng {
11976 name: "refs/heads/old".into(),
11977 message: "non-fast-forward".into(),
11978 },
11979 ],
11980 }
11981 );
11982 assert_eq!(
11983 encode_receive_pack_report_status(&report).expect("test operation should succeed"),
11984 frames
11985 );
11986
11987 let frames = vec![
11988 PktLineFrame::Data(b"unpack pack exceeds maximum size\n".to_vec()),
11989 PktLineFrame::Flush,
11990 ];
11991 assert_eq!(
11992 parse_receive_pack_report_status(&frames).expect("test operation should succeed"),
11993 ReceivePackReportStatus {
11994 unpack: ReceivePackUnpackStatus::Error("pack exceeds maximum size".into()),
11995 commands: Vec::new(),
11996 }
11997 );
11998 }
11999
12000 #[test]
12001 fn receive_pack_report_status_streams_round_trip() {
12002 let report = ReceivePackReportStatus {
12003 unpack: ReceivePackUnpackStatus::Ok,
12004 commands: vec![ReceivePackCommandStatus::Ok {
12005 name: "refs/heads/main".into(),
12006 }],
12007 };
12008 let mut encoded = Vec::new();
12009 write_receive_pack_report_status(&mut encoded, &report)
12010 .expect("test operation should succeed");
12011 encoded.extend_from_slice(b"tail");
12012
12013 let mut input = encoded.as_slice();
12014 assert_eq!(
12015 read_receive_pack_report_status(&mut input).expect("test operation should succeed"),
12016 report
12017 );
12018 assert_eq!(input, b"tail");
12019 }
12020
12021 #[test]
12022 fn receive_pack_report_status_rejects_malformed_status_lines() {
12023 assert!(parse_receive_pack_report_status(&[]).is_err());
12024 assert!(
12025 parse_receive_pack_report_status(&[
12026 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12027 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12028 ])
12029 .is_err()
12030 );
12031 assert!(
12032 parse_receive_pack_report_status(&[
12033 PktLineFrame::Flush,
12034 PktLineFrame::Data(b"ok refs/heads/main\n".to_vec()),
12035 ])
12036 .is_err()
12037 );
12038 assert!(
12039 parse_receive_pack_report_status(&[
12040 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12041 PktLineFrame::Data(b"bad refs/heads/main\n".to_vec()),
12042 PktLineFrame::Flush,
12043 ])
12044 .is_err()
12045 );
12046 assert!(
12047 parse_receive_pack_report_status(&[
12048 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12049 PktLineFrame::Data(b"ng refs/heads/main\n".to_vec()),
12050 PktLineFrame::Flush,
12051 ])
12052 .is_err()
12053 );
12054 assert!(
12055 encode_receive_pack_report_status(&ReceivePackReportStatus {
12056 unpack: ReceivePackUnpackStatus::Error("".into()),
12057 commands: Vec::new(),
12058 })
12059 .is_err()
12060 );
12061 assert!(
12062 encode_receive_pack_report_status(&ReceivePackReportStatus {
12063 unpack: ReceivePackUnpackStatus::Ok,
12064 commands: vec![ReceivePackCommandStatus::Ok {
12065 name: "bad ref".into(),
12066 }],
12067 })
12068 .is_err()
12069 );
12070 }
12071
12072 #[test]
12073 fn receive_pack_report_status_v2_parses_and_encodes_options() {
12074 let old_oid = ObjectId::from_hex(
12075 ObjectFormat::Sha1,
12076 "1111111111111111111111111111111111111111",
12077 )
12078 .expect("test operation should succeed");
12079 let new_oid = ObjectId::from_hex(
12080 ObjectFormat::Sha1,
12081 "2222222222222222222222222222222222222222",
12082 )
12083 .expect("test operation should succeed");
12084 let frames = vec![
12085 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12086 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12087 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12088 PktLineFrame::Data(
12089 b"option old-oid 1111111111111111111111111111111111111111\n".to_vec(),
12090 ),
12091 PktLineFrame::Data(
12092 b"option new-oid 2222222222222222222222222222222222222222\n".to_vec(),
12093 ),
12094 PktLineFrame::Data(b"option forced-update\n".to_vec()),
12095 PktLineFrame::Data(b"ng refs/heads/old rejected by hook\n".to_vec()),
12096 PktLineFrame::Flush,
12097 ];
12098 let report = parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &frames)
12099 .expect("test operation should succeed");
12100 assert_eq!(
12101 report,
12102 ReceivePackReportStatusV2 {
12103 unpack: ReceivePackUnpackStatus::Ok,
12104 commands: vec![
12105 ReceivePackCommandStatusV2::Ok {
12106 name: "refs/for/main".into(),
12107 options: ReceivePackCommandStatusV2Options {
12108 refname: Some("refs/heads/main".into()),
12109 old_oid: Some(old_oid),
12110 new_oid: Some(new_oid),
12111 forced_update: true,
12112 },
12113 },
12114 ReceivePackCommandStatusV2::Ng {
12115 name: "refs/heads/old".into(),
12116 message: "rejected by hook".into(),
12117 },
12118 ],
12119 }
12120 );
12121 assert_eq!(
12122 encode_receive_pack_report_status_v2(&report).expect("test operation should succeed"),
12123 frames
12124 );
12125 }
12126
12127 #[test]
12128 fn receive_pack_report_status_v2_streams_round_trip() {
12129 let report = ReceivePackReportStatusV2 {
12130 unpack: ReceivePackUnpackStatus::Ok,
12131 commands: vec![ReceivePackCommandStatusV2::Ok {
12132 name: "refs/for/main".into(),
12133 options: ReceivePackCommandStatusV2Options {
12134 refname: Some("refs/heads/main".into()),
12135 old_oid: None,
12136 new_oid: None,
12137 forced_update: false,
12138 },
12139 }],
12140 };
12141 let mut encoded = Vec::new();
12142 write_receive_pack_report_status_v2(&mut encoded, &report)
12143 .expect("test operation should succeed");
12144 encoded.extend_from_slice(b"tail");
12145
12146 let mut input = encoded.as_slice();
12147 assert_eq!(
12148 read_receive_pack_report_status_v2(ObjectFormat::Sha1, &mut input)
12149 .expect("test operation should succeed"),
12150 report
12151 );
12152 assert_eq!(input, b"tail");
12153 }
12154
12155 #[test]
12156 fn receive_pack_report_status_v2_rejects_malformed_options() {
12157 assert!(parse_receive_pack_report_status_v2(ObjectFormat::Sha1, &[]).is_err());
12158 assert!(
12159 parse_receive_pack_report_status_v2(
12160 ObjectFormat::Sha1,
12161 &[
12162 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12163 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12164 PktLineFrame::Flush,
12165 ],
12166 )
12167 .is_err()
12168 );
12169 assert!(
12170 parse_receive_pack_report_status_v2(
12171 ObjectFormat::Sha1,
12172 &[
12173 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12174 PktLineFrame::Data(b"ng refs/heads/main rejected\n".to_vec()),
12175 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12176 PktLineFrame::Flush,
12177 ],
12178 )
12179 .is_err()
12180 );
12181 assert!(
12182 parse_receive_pack_report_status_v2(
12183 ObjectFormat::Sha1,
12184 &[
12185 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12186 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12187 PktLineFrame::Data(b"option refname refs/heads/main\n".to_vec()),
12188 PktLineFrame::Data(b"option refname refs/heads/next\n".to_vec()),
12189 PktLineFrame::Flush,
12190 ],
12191 )
12192 .is_err()
12193 );
12194 assert!(
12195 parse_receive_pack_report_status_v2(
12196 ObjectFormat::Sha1,
12197 &[
12198 PktLineFrame::Data(b"unpack ok\n".to_vec()),
12199 PktLineFrame::Data(b"ok refs/for/main\n".to_vec()),
12200 PktLineFrame::Data(b"option old-oid not-an-oid\n".to_vec()),
12201 PktLineFrame::Flush,
12202 ],
12203 )
12204 .is_err()
12205 );
12206 assert!(
12207 encode_receive_pack_report_status_v2(&ReceivePackReportStatusV2 {
12208 unpack: ReceivePackUnpackStatus::Ok,
12209 commands: vec![ReceivePackCommandStatusV2::Ok {
12210 name: "refs/for/main".into(),
12211 options: ReceivePackCommandStatusV2Options {
12212 refname: Some("bad ref".into()),
12213 ..ReceivePackCommandStatusV2Options::default()
12214 },
12215 }],
12216 })
12217 .is_err()
12218 );
12219 }
12220
12221 #[test]
12222 fn receive_pack_push_options_parse_and_encode_options() {
12223 let frames = vec![
12224 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12225 PktLineFrame::Data(b"deploy target=staging\n".to_vec()),
12226 PktLineFrame::Data(b"\n".to_vec()),
12227 PktLineFrame::Flush,
12228 ];
12229 let options =
12230 parse_receive_pack_push_options(&frames).expect("test operation should succeed");
12231 assert_eq!(
12232 options,
12233 vec![
12234 "ci.skip".to_string(),
12235 "deploy target=staging".to_string(),
12236 String::new(),
12237 ]
12238 );
12239 assert_eq!(
12240 encode_receive_pack_push_options(&options).expect("test operation should succeed"),
12241 frames
12242 );
12243 assert_eq!(
12244 parse_receive_pack_push_options(&[PktLineFrame::Flush])
12245 .expect("test operation should succeed"),
12246 Vec::<String>::new()
12247 );
12248 }
12249
12250 #[test]
12251 fn receive_pack_push_options_streams_round_trip() {
12252 let options = vec!["ci.skip".to_string(), "reviewer=alice".to_string()];
12253 let mut encoded = Vec::new();
12254 write_receive_pack_push_options(&mut encoded, &options)
12255 .expect("test operation should succeed");
12256 encoded.extend_from_slice(b"PACK");
12257
12258 let mut input = encoded.as_slice();
12259 assert_eq!(
12260 read_receive_pack_push_options(&mut input).expect("test operation should succeed"),
12261 options
12262 );
12263 assert_eq!(input, b"PACK");
12264 }
12265
12266 #[test]
12267 fn receive_pack_push_options_reject_malformed_streams() {
12268 assert!(
12269 parse_receive_pack_push_options(&[PktLineFrame::Data(b"ci.skip\n".to_vec())]).is_err()
12270 );
12271 assert!(
12272 parse_receive_pack_push_options(&[PktLineFrame::Delimiter, PktLineFrame::Flush])
12273 .is_err()
12274 );
12275 assert!(
12276 parse_receive_pack_push_options(&[
12277 PktLineFrame::Data(b"ci.skip\n".to_vec()),
12278 PktLineFrame::Flush,
12279 PktLineFrame::Data(b"after\n".to_vec()),
12280 ])
12281 .is_err()
12282 );
12283 assert!(
12284 parse_receive_pack_push_options(&[
12285 PktLineFrame::Data(b"bad\0option\n".to_vec()),
12286 PktLineFrame::Flush,
12287 ])
12288 .is_err()
12289 );
12290 assert!(encode_receive_pack_push_options(&["bad\noption".to_string()]).is_err());
12291 }
12292
12293 #[test]
12294 fn protocol_v2_advertisement_parses_version_and_capabilities() {
12295 let frames = parse_pkt_line_stream(
12296 b"000eversion 2\n0015agent=git/2.54.0\n0013ls-refs=unborn\n0027fetch=shallow wait-for-done filter\n0012server-option\n0000",
12297 )
12298 .expect("test operation should succeed");
12299 let handshake =
12300 parse_protocol_v2_advertisement(&frames).expect("test operation should succeed");
12301 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12302 assert_eq!(
12303 handshake.capabilities,
12304 vec![
12305 Capability {
12306 name: "agent".into(),
12307 value: Some("git/2.54.0".into()),
12308 },
12309 Capability {
12310 name: "ls-refs".into(),
12311 value: Some("unborn".into()),
12312 },
12313 Capability {
12314 name: "fetch".into(),
12315 value: Some("shallow wait-for-done filter".into()),
12316 },
12317 Capability {
12318 name: "server-option".into(),
12319 value: None,
12320 },
12321 ]
12322 );
12323 assert_eq!(
12324 encode_protocol_v2_advertisement(&handshake).expect("test operation should succeed"),
12325 frames
12326 );
12327 }
12328
12329 #[test]
12330 fn protocol_v2_advertisement_reads_until_flush() {
12331 let mut input = b"000eversion 2\n0013ls-refs=unborn\n0000next-session".as_slice();
12332 let handshake =
12333 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed");
12334 assert_eq!(handshake.protocol, ProtocolVersion::V2);
12335 assert_eq!(
12336 handshake.capabilities,
12337 vec![Capability {
12338 name: "ls-refs".into(),
12339 value: Some("unborn".into()),
12340 }]
12341 );
12342 assert_eq!(input, b"next-session");
12343 }
12344
12345 #[test]
12346 fn protocol_v2_advertisement_writes_stream() {
12347 let handshake = TransportHandshake {
12348 protocol: ProtocolVersion::V2,
12349 capabilities: vec![
12350 Capability {
12351 name: "agent".into(),
12352 value: Some("sley/0".into()),
12353 },
12354 Capability {
12355 name: "fetch".into(),
12356 value: Some("shallow filter".into()),
12357 },
12358 ],
12359 };
12360 let mut encoded = Vec::new();
12361 write_protocol_v2_advertisement(&mut encoded, &handshake)
12362 .expect("test operation should succeed");
12363 let mut input = encoded.as_slice();
12364 assert_eq!(
12365 read_protocol_v2_advertisement(&mut input).expect("test operation should succeed"),
12366 handshake
12367 );
12368 assert!(input.is_empty());
12369 assert!(
12370 encode_protocol_v2_advertisement(&TransportHandshake {
12371 protocol: ProtocolVersion::V1,
12372 capabilities: Vec::new(),
12373 })
12374 .is_err()
12375 );
12376 }
12377
12378 #[test]
12379 fn protocol_v2_advertisement_rejects_malformed_sequences() {
12380 assert!(parse_protocol_v2_advertisement(&[]).is_err());
12381 assert!(
12382 parse_protocol_v2_advertisement(&[
12383 PktLineFrame::Data(b"version 1\n".to_vec()),
12384 PktLineFrame::Flush,
12385 ])
12386 .is_err()
12387 );
12388 assert!(
12389 parse_protocol_v2_advertisement(&[PktLineFrame::Data(b"version 2\n".to_vec())])
12390 .is_err()
12391 );
12392 assert!(
12393 parse_protocol_v2_advertisement(&[
12394 PktLineFrame::Data(b"version 2\n".to_vec()),
12395 PktLineFrame::Delimiter,
12396 ])
12397 .is_err()
12398 );
12399 assert!(
12400 parse_protocol_v2_advertisement(&[
12401 PktLineFrame::Data(b"version 2\n".to_vec()),
12402 PktLineFrame::Data(b"fetch=\n".to_vec()),
12403 PktLineFrame::Flush,
12404 ])
12405 .is_err()
12406 );
12407 }
12408
12409 #[test]
12410 fn protocol_v2_command_request_parses_and_encodes_sections() {
12411 let frames = parse_pkt_line_stream(
12412 b"0014command=ls-refs\n0011agent=sley/0\n0017object-format=sha1\n00010009peel\n000csymrefs\n001bref-prefix refs/heads/\n0000",
12413 )
12414 .expect("test operation should succeed");
12415 let request =
12416 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12417 assert_eq!(
12418 request,
12419 ProtocolV2CommandRequest {
12420 command: "ls-refs".into(),
12421 capabilities: vec![
12422 Capability {
12423 name: "agent".into(),
12424 value: Some("sley/0".into()),
12425 },
12426 Capability {
12427 name: "object-format".into(),
12428 value: Some("sha1".into()),
12429 },
12430 ],
12431 arguments: vec![
12432 b"peel".to_vec(),
12433 b"symrefs".to_vec(),
12434 b"ref-prefix refs/heads/".to_vec(),
12435 ],
12436 }
12437 );
12438 assert_eq!(
12439 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12440 frames
12441 );
12442 }
12443
12444 #[test]
12445 fn protocol_v2_command_request_allows_no_argument_section() {
12446 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12447 .expect("test operation should succeed");
12448 let request =
12449 parse_protocol_v2_command_request(&frames).expect("test operation should succeed");
12450 assert_eq!(
12451 request,
12452 ProtocolV2CommandRequest {
12453 command: "fetch".into(),
12454 capabilities: Vec::new(),
12455 arguments: Vec::new(),
12456 }
12457 );
12458 assert_eq!(
12459 encode_protocol_v2_command_request(&request).expect("test operation should succeed"),
12460 frames
12461 );
12462 }
12463
12464 #[test]
12465 fn protocol_v2_request_parses_commands_and_empty_done() {
12466 let frames = parse_pkt_line_stream(b"0012command=fetch\n0000")
12467 .expect("test operation should succeed");
12468 let command = ProtocolV2CommandRequest {
12469 command: "fetch".into(),
12470 capabilities: Vec::new(),
12471 arguments: Vec::new(),
12472 };
12473 assert_eq!(
12474 parse_protocol_v2_request(&frames).expect("test operation should succeed"),
12475 ProtocolV2Request::Command(command.clone())
12476 );
12477 assert_eq!(
12478 encode_protocol_v2_request(&ProtocolV2Request::Command(command))
12479 .expect("test operation should succeed"),
12480 frames
12481 );
12482
12483 assert_eq!(
12484 parse_protocol_v2_request(&[PktLineFrame::Flush])
12485 .expect("test operation should succeed"),
12486 ProtocolV2Request::Done
12487 );
12488 assert_eq!(
12489 encode_protocol_v2_request(&ProtocolV2Request::Done)
12490 .expect("test operation should succeed"),
12491 vec![PktLineFrame::Flush]
12492 );
12493 }
12494
12495 #[test]
12496 fn protocol_v2_request_streams_empty_done() {
12497 let mut encoded = Vec::new();
12498 write_protocol_v2_request(&mut encoded, &ProtocolV2Request::Done)
12499 .expect("test operation should succeed");
12500 encoded.extend_from_slice(b"tail");
12501
12502 let mut input = encoded.as_slice();
12503 assert_eq!(
12504 read_protocol_v2_request(&mut input).expect("test operation should succeed"),
12505 ProtocolV2Request::Done
12506 );
12507 assert_eq!(input, b"tail");
12508 let mut command_input = encoded.as_slice();
12509 assert!(read_protocol_v2_command_request(&mut command_input).is_err());
12510 }
12511
12512 #[test]
12513 fn protocol_v2_command_request_streams_round_trip() {
12514 let request = ProtocolV2CommandRequest {
12515 command: "ls-refs".into(),
12516 capabilities: vec![Capability {
12517 name: "agent".into(),
12518 value: Some("sley/0".into()),
12519 }],
12520 arguments: vec![b"peel".to_vec(), b"symrefs".to_vec()],
12521 };
12522 let mut encoded = Vec::new();
12523 write_protocol_v2_command_request(&mut encoded, &request)
12524 .expect("test operation should succeed");
12525 encoded.extend_from_slice(b"tail");
12526
12527 let mut input = encoded.as_slice();
12528 assert_eq!(
12529 read_protocol_v2_command_request(&mut input).expect("test operation should succeed"),
12530 request
12531 );
12532 assert_eq!(input, b"tail");
12533 }
12534
12535 #[test]
12536 fn protocol_v2_command_request_rejects_malformed_sequences() {
12537 assert!(parse_protocol_v2_command_request(&[]).is_err());
12538 assert!(
12539 parse_protocol_v2_command_request(&[
12540 PktLineFrame::Data(b"agent=sley/0\n".to_vec()),
12541 PktLineFrame::Flush,
12542 ])
12543 .is_err()
12544 );
12545 assert!(
12546 parse_protocol_v2_command_request(&[
12547 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12548 PktLineFrame::Delimiter,
12549 PktLineFrame::Delimiter,
12550 PktLineFrame::Flush,
12551 ])
12552 .is_err()
12553 );
12554 assert!(
12555 parse_protocol_v2_command_request(&[
12556 PktLineFrame::Data(b"command=ls-refs\n".to_vec()),
12557 PktLineFrame::Delimiter,
12558 PktLineFrame::Data(b"\n".to_vec()),
12559 PktLineFrame::Flush,
12560 ])
12561 .is_err()
12562 );
12563 assert!(
12564 encode_protocol_v2_command_request(&ProtocolV2CommandRequest {
12565 command: "bad command".into(),
12566 capabilities: Vec::new(),
12567 arguments: Vec::new(),
12568 })
12569 .is_err()
12570 );
12571 }
12572
12573 #[test]
12574 fn protocol_v2_ls_refs_request_parses_and_encodes_arguments() {
12575 let command = ProtocolV2CommandRequest {
12576 command: "ls-refs".into(),
12577 capabilities: Vec::new(),
12578 arguments: vec![
12579 b"peel".to_vec(),
12580 b"symrefs".to_vec(),
12581 b"unborn".to_vec(),
12582 b"ref-prefix HEAD".to_vec(),
12583 b"ref-prefix refs/heads/".to_vec(),
12584 ],
12585 };
12586 let request = ProtocolV2LsRefsRequest::from_command_request(&command)
12587 .expect("test operation should succeed");
12588 assert_eq!(
12589 request,
12590 ProtocolV2LsRefsRequest {
12591 peel: true,
12592 symrefs: true,
12593 unborn: true,
12594 ref_prefixes: vec!["HEAD".into(), "refs/heads/".into()],
12595 }
12596 );
12597 assert_eq!(
12598 request
12599 .to_command_request()
12600 .expect("test operation should succeed"),
12601 command
12602 );
12603 assert!(
12604 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12605 command: "fetch".into(),
12606 capabilities: Vec::new(),
12607 arguments: Vec::new(),
12608 })
12609 .is_err()
12610 );
12611 assert!(
12612 ProtocolV2LsRefsRequest::from_command_request(&ProtocolV2CommandRequest {
12613 command: "ls-refs".into(),
12614 capabilities: Vec::new(),
12615 arguments: vec![b"ref-prefix ".to_vec()],
12616 })
12617 .is_err()
12618 );
12619 }
12620
12621 #[test]
12622 fn protocol_v2_ls_refs_request_streams_round_trip() {
12623 let request = ProtocolV2LsRefsRequest {
12624 peel: true,
12625 symrefs: true,
12626 unborn: false,
12627 ref_prefixes: vec!["HEAD".into(), "refs/tags/".into()],
12628 };
12629 let mut encoded = Vec::new();
12630 write_protocol_v2_ls_refs_request(&mut encoded, &request)
12631 .expect("test operation should succeed");
12632 encoded.extend_from_slice(b"tail");
12633
12634 let mut input = encoded.as_slice();
12635 assert_eq!(
12636 read_protocol_v2_ls_refs_request(&mut input).expect("test operation should succeed"),
12637 request
12638 );
12639 assert_eq!(input, b"tail");
12640 }
12641
12642 #[test]
12643 fn protocol_v2_ls_refs_response_parses_and_encodes_records() {
12644 let oid = ObjectId::from_hex(
12645 ObjectFormat::Sha1,
12646 "1111111111111111111111111111111111111111",
12647 )
12648 .expect("test operation should succeed");
12649 let peeled = ObjectId::from_hex(
12650 ObjectFormat::Sha1,
12651 "2222222222222222222222222222222222222222",
12652 )
12653 .expect("test operation should succeed");
12654 let frames = vec![
12655 PktLineFrame::Data(
12656 b"1111111111111111111111111111111111111111 refs/tags/v1 peeled:2222222222222222222222222222222222222222 symref-target:refs/heads/main custom\n"
12657 .to_vec(),
12658 ),
12659 PktLineFrame::Data(b"unborn HEAD symref-target:refs/heads/main\n".to_vec()),
12660 PktLineFrame::Flush,
12661 ];
12662 let records = parse_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &frames)
12663 .expect("test operation should succeed");
12664 assert_eq!(
12665 records,
12666 vec![
12667 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12668 oid,
12669 name: "refs/tags/v1".into(),
12670 peeled: Some(peeled),
12671 symref_target: Some("refs/heads/main".into()),
12672 attributes: vec!["custom".into()],
12673 }),
12674 ProtocolV2LsRefsRecord::Unborn {
12675 name: "HEAD".into(),
12676 symref_target: Some("refs/heads/main".into()),
12677 attributes: Vec::new(),
12678 },
12679 ]
12680 );
12681 assert_eq!(
12682 encode_protocol_v2_ls_refs_response(&records).expect("test operation should succeed"),
12683 frames
12684 );
12685 }
12686
12687 #[test]
12688 fn protocol_v2_ls_refs_response_streams_round_trip() {
12689 let oid = ObjectId::from_hex(
12690 ObjectFormat::Sha1,
12691 "1111111111111111111111111111111111111111",
12692 )
12693 .expect("test operation should succeed");
12694 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12695 oid,
12696 name: "refs/heads/main".into(),
12697 peeled: None,
12698 symref_target: Some("refs/heads/trunk".into()),
12699 attributes: vec!["custom".into()],
12700 })];
12701 let mut encoded = Vec::new();
12702 write_protocol_v2_ls_refs_response(&mut encoded, &records)
12703 .expect("test operation should succeed");
12704 encoded.extend_from_slice(b"tail");
12705
12706 let mut input = encoded.as_slice();
12707 assert_eq!(
12708 read_protocol_v2_ls_refs_response(ObjectFormat::Sha1, &mut input)
12709 .expect("test operation should succeed"),
12710 records
12711 );
12712 assert_eq!(input, b"tail");
12713 }
12714
12715 #[test]
12716 fn protocol_v2_ls_refs_response_reads_stateless_response_end() {
12717 let oid = ObjectId::from_hex(
12718 ObjectFormat::Sha1,
12719 "1111111111111111111111111111111111111111",
12720 )
12721 .expect("test operation should succeed");
12722 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12723 oid,
12724 name: "refs/heads/main".into(),
12725 peeled: None,
12726 symref_target: None,
12727 attributes: Vec::new(),
12728 })];
12729 let mut encoded = Vec::new();
12730 write_protocol_v2_ls_refs_response_with_response_end(&mut encoded, &records)
12731 .expect("test operation should succeed");
12732 encoded.extend_from_slice(b"tail");
12733
12734 let mut input = encoded.as_slice();
12735 assert_eq!(
12736 read_protocol_v2_ls_refs_response_until_response_end(ObjectFormat::Sha1, &mut input)
12737 .expect("test operation should succeed"),
12738 records
12739 );
12740 assert_eq!(input, b"tail");
12741 assert!(
12742 parse_protocol_v2_ls_refs_response(
12743 ObjectFormat::Sha1,
12744 &[
12745 PktLineFrame::Data(
12746 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
12747 ),
12748 PktLineFrame::ResponseEnd
12749 ],
12750 )
12751 .is_err()
12752 );
12753 }
12754
12755 #[test]
12756 fn protocol_v2_ls_refs_exchange_writes_request_and_reads_response() {
12757 let oid = ObjectId::from_hex(
12758 ObjectFormat::Sha1,
12759 "1111111111111111111111111111111111111111",
12760 )
12761 .expect("test operation should succeed");
12762 let request = ProtocolV2LsRefsRequest {
12763 peel: true,
12764 symrefs: true,
12765 unborn: false,
12766 ref_prefixes: vec!["refs/heads/".into()],
12767 };
12768 let records = vec![ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
12769 oid,
12770 name: "refs/heads/main".into(),
12771 peeled: None,
12772 symref_target: None,
12773 attributes: Vec::new(),
12774 })];
12775 let mut response = Vec::new();
12776 write_protocol_v2_ls_refs_response(&mut response, &records)
12777 .expect("test operation should succeed");
12778
12779 let mut input = response.as_slice();
12780 let mut output = Vec::new();
12781 assert_eq!(
12782 exchange_protocol_v2_ls_refs(ObjectFormat::Sha1, &mut input, &mut output, &request)
12783 .expect("test operation should succeed"),
12784 records
12785 );
12786 assert!(input.is_empty());
12787 let mut output_read = output.as_slice();
12788 assert_eq!(
12789 read_protocol_v2_ls_refs_request(&mut output_read)
12790 .expect("test operation should succeed"),
12791 request
12792 );
12793 }
12794
12795 #[test]
12796 fn protocol_v2_ls_refs_response_rejects_malformed_records() {
12797 assert!(
12798 parse_protocol_v2_ls_refs_response(
12799 ObjectFormat::Sha1,
12800 &[PktLineFrame::Data(
12801 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec()
12802 )],
12803 )
12804 .is_err()
12805 );
12806 assert!(
12807 parse_protocol_v2_ls_refs_response(
12808 ObjectFormat::Sha1,
12809 &[
12810 PktLineFrame::Data(
12811 b"1111111111111111111111111111111111111111 refs/heads/main peeled:2222222222222222222222222222222222222222 peeled:3333333333333333333333333333333333333333\n"
12812 .to_vec()
12813 ),
12814 PktLineFrame::Flush,
12815 ],
12816 )
12817 .is_err()
12818 );
12819 assert!(
12820 parse_protocol_v2_ls_refs_response(
12821 ObjectFormat::Sha1,
12822 &[
12823 PktLineFrame::Data(
12824 b"unborn HEAD peeled:2222222222222222222222222222222222222222\n".to_vec()
12825 ),
12826 PktLineFrame::Flush,
12827 ],
12828 )
12829 .is_err()
12830 );
12831 assert!(
12832 encode_protocol_v2_ls_refs_response(&[ProtocolV2LsRefsRecord::Ref(
12833 ProtocolV2LsRefsRef {
12834 oid: ObjectId::from_hex(
12835 ObjectFormat::Sha1,
12836 "1111111111111111111111111111111111111111",
12837 )
12838 .expect("test operation should succeed"),
12839 name: "refs/heads/main".into(),
12840 peeled: None,
12841 symref_target: None,
12842 attributes: vec!["peeled:2222222222222222222222222222222222222222".into()],
12843 }
12844 )])
12845 .is_err()
12846 );
12847 }
12848
12849 #[test]
12850 fn protocol_v2_fetch_request_parses_and_encodes_arguments() {
12851 let want = ObjectId::from_hex(
12852 ObjectFormat::Sha1,
12853 "1111111111111111111111111111111111111111",
12854 )
12855 .expect("test operation should succeed");
12856 let have = ObjectId::from_hex(
12857 ObjectFormat::Sha1,
12858 "2222222222222222222222222222222222222222",
12859 )
12860 .expect("test operation should succeed");
12861 let shallow = ObjectId::from_hex(
12862 ObjectFormat::Sha1,
12863 "3333333333333333333333333333333333333333",
12864 )
12865 .expect("test operation should succeed");
12866 let command = ProtocolV2CommandRequest {
12867 command: "fetch".into(),
12868 capabilities: Vec::new(),
12869 arguments: vec![
12870 b"want 1111111111111111111111111111111111111111".to_vec(),
12871 b"want-ref refs/heads/main".to_vec(),
12872 b"have 2222222222222222222222222222222222222222".to_vec(),
12873 b"shallow 3333333333333333333333333333333333333333".to_vec(),
12874 b"deepen 10".to_vec(),
12875 b"deepen-since 123456789".to_vec(),
12876 b"deepen-not refs/tags/v1".to_vec(),
12877 b"deepen-relative".to_vec(),
12878 b"filter blob:none".to_vec(),
12879 b"packfile-uris http,https".to_vec(),
12880 b"thin-pack".to_vec(),
12881 b"no-progress".to_vec(),
12882 b"include-tag".to_vec(),
12883 b"ofs-delta".to_vec(),
12884 b"sideband-all".to_vec(),
12885 b"wait-for-done".to_vec(),
12886 b"done".to_vec(),
12887 ],
12888 };
12889 let request = ProtocolV2FetchRequest::from_command_request(ObjectFormat::Sha1, &command)
12890 .expect("test operation should succeed");
12891 assert_eq!(
12892 request,
12893 ProtocolV2FetchRequest {
12894 wants: vec![want],
12895 want_refs: vec!["refs/heads/main".into()],
12896 haves: vec![have],
12897 shallow: vec![shallow],
12898 deepen: Some(10),
12899 deepen_since: Some(123456789),
12900 deepen_not: vec!["refs/tags/v1".into()],
12901 deepen_relative: true,
12902 filter: Some("blob:none".into()),
12903 packfile_uris: Some("http,https".into()),
12904 thin_pack: true,
12905 no_progress: true,
12906 include_tag: true,
12907 ofs_delta: true,
12908 sideband_all: true,
12909 wait_for_done: true,
12910 done: true,
12911 }
12912 );
12913 assert_eq!(
12914 request
12915 .to_command_request()
12916 .expect("test operation should succeed"),
12917 command
12918 );
12919 }
12920
12921 #[test]
12922 fn protocol_v2_fetch_request_rejects_malformed_arguments() {
12923 assert!(
12924 ProtocolV2FetchRequest::from_command_request(
12925 ObjectFormat::Sha1,
12926 &ProtocolV2CommandRequest {
12927 command: "ls-refs".into(),
12928 capabilities: Vec::new(),
12929 arguments: Vec::new(),
12930 },
12931 )
12932 .is_err()
12933 );
12934 assert!(
12935 ProtocolV2FetchRequest::from_command_request(
12936 ObjectFormat::Sha1,
12937 &ProtocolV2CommandRequest {
12938 command: "fetch".into(),
12939 capabilities: Vec::new(),
12940 arguments: vec![b"want not-an-oid".to_vec()],
12941 },
12942 )
12943 .is_err()
12944 );
12945 assert!(
12946 ProtocolV2FetchRequest::from_command_request(
12947 ObjectFormat::Sha1,
12948 &ProtocolV2CommandRequest {
12949 command: "fetch".into(),
12950 capabilities: Vec::new(),
12951 arguments: vec![b"deepen 0".to_vec()],
12952 },
12953 )
12954 .is_err()
12955 );
12956 assert!(
12957 ProtocolV2FetchRequest::from_command_request(
12958 ObjectFormat::Sha1,
12959 &ProtocolV2CommandRequest {
12960 command: "fetch".into(),
12961 capabilities: Vec::new(),
12962 arguments: vec![b"filter blob:none".to_vec(), b"filter tree:0".to_vec()],
12963 },
12964 )
12965 .is_err()
12966 );
12967 assert!(
12968 ProtocolV2FetchRequest {
12969 deepen: Some(0),
12970 ..ProtocolV2FetchRequest::default()
12971 }
12972 .to_command_request()
12973 .is_err()
12974 );
12975 }
12976
12977 #[test]
12978 fn protocol_v2_fetch_request_streams_round_trip() {
12979 let want = ObjectId::from_hex(
12980 ObjectFormat::Sha1,
12981 "1111111111111111111111111111111111111111",
12982 )
12983 .expect("test operation should succeed");
12984 let have = ObjectId::from_hex(
12985 ObjectFormat::Sha1,
12986 "2222222222222222222222222222222222222222",
12987 )
12988 .expect("test operation should succeed");
12989 let request = ProtocolV2FetchRequest {
12990 wants: vec![want],
12991 haves: vec![have],
12992 deepen: Some(5),
12993 filter: Some("blob:none".into()),
12994 thin_pack: true,
12995 done: true,
12996 ..ProtocolV2FetchRequest::default()
12997 };
12998 let mut encoded = Vec::new();
12999 write_protocol_v2_fetch_request(&mut encoded, &request)
13000 .expect("test operation should succeed");
13001 encoded.extend_from_slice(b"tail");
13002
13003 let mut input = encoded.as_slice();
13004 assert_eq!(
13005 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut input)
13006 .expect("test operation should succeed"),
13007 request
13008 );
13009 assert_eq!(input, b"tail");
13010 }
13011
13012 #[test]
13013 fn protocol_v2_fetch_response_parses_and_encodes_sections() {
13014 let ack = ObjectId::from_hex(
13015 ObjectFormat::Sha1,
13016 "1111111111111111111111111111111111111111",
13017 )
13018 .expect("test operation should succeed");
13019 let shallow = ObjectId::from_hex(
13020 ObjectFormat::Sha1,
13021 "2222222222222222222222222222222222222222",
13022 )
13023 .expect("test operation should succeed");
13024 let wanted = ObjectId::from_hex(
13025 ObjectFormat::Sha1,
13026 "3333333333333333333333333333333333333333",
13027 )
13028 .expect("test operation should succeed");
13029 let pack_hash = ObjectId::from_hex(
13030 ObjectFormat::Sha1,
13031 "4444444444444444444444444444444444444444",
13032 )
13033 .expect("test operation should succeed");
13034 let frames = vec![
13035 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13036 PktLineFrame::Data(b"ACK 1111111111111111111111111111111111111111\n".to_vec()),
13037 PktLineFrame::Data(b"ready\n".to_vec()),
13038 PktLineFrame::Delimiter,
13039 PktLineFrame::Data(b"shallow-info\n".to_vec()),
13040 PktLineFrame::Data(b"shallow 2222222222222222222222222222222222222222\n".to_vec()),
13041 PktLineFrame::Delimiter,
13042 PktLineFrame::Data(b"wanted-refs\n".to_vec()),
13043 PktLineFrame::Data(
13044 b"3333333333333333333333333333333333333333 refs/heads/main\n".to_vec(),
13045 ),
13046 PktLineFrame::Delimiter,
13047 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13048 PktLineFrame::Data(
13049 b"4444444444444444444444444444444444444444 https://example.invalid/pack-a.pack\n"
13050 .to_vec(),
13051 ),
13052 PktLineFrame::Delimiter,
13053 PktLineFrame::Data(b"packfile\n".to_vec()),
13054 PktLineFrame::Data(b"\x01PACK bytes".to_vec()),
13055 PktLineFrame::Flush,
13056 ];
13057 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13058 .expect("test operation should succeed");
13059 assert_eq!(
13060 sections,
13061 vec![
13062 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13063 ProtocolV2FetchAcknowledgment::Ack(ack),
13064 ProtocolV2FetchAcknowledgment::Ready,
13065 ]),
13066 ProtocolV2FetchResponseSection::ShallowInfo(vec![
13067 ProtocolV2FetchShallowInfo::Shallow(shallow)
13068 ]),
13069 ProtocolV2FetchResponseSection::WantedRefs(vec![ProtocolV2FetchWantedRef {
13070 oid: wanted,
13071 name: "refs/heads/main".into(),
13072 }]),
13073 ProtocolV2FetchResponseSection::PackfileUris(vec![ProtocolV2FetchPackfileUri {
13074 pack_hash,
13075 uri: "https://example.invalid/pack-a.pack".into(),
13076 }]),
13077 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13078 ]
13079 );
13080 assert_eq!(
13081 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13082 frames
13083 );
13084 }
13085
13086 #[test]
13087 fn protocol_v2_fetch_response_preserves_unknown_sections() {
13088 let frames = vec![
13089 PktLineFrame::Data(b"server-feature\n".to_vec()),
13090 PktLineFrame::Data(b"opaque line\n".to_vec()),
13091 PktLineFrame::Flush,
13092 ];
13093 let sections = parse_protocol_v2_fetch_response(ObjectFormat::Sha1, &frames)
13094 .expect("test operation should succeed");
13095 assert_eq!(
13096 sections,
13097 vec![ProtocolV2FetchResponseSection::Unknown {
13098 name: "server-feature".into(),
13099 lines: vec![b"opaque line\n".to_vec()],
13100 }]
13101 );
13102 assert_eq!(
13103 encode_protocol_v2_fetch_response(§ions).expect("test operation should succeed"),
13104 frames
13105 );
13106 }
13107
13108 #[test]
13109 fn protocol_v2_fetch_response_streams_round_trip() {
13110 let ack = ObjectId::from_hex(
13111 ObjectFormat::Sha1,
13112 "1111111111111111111111111111111111111111",
13113 )
13114 .expect("test operation should succeed");
13115 let sections = vec![
13116 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13117 ProtocolV2FetchAcknowledgment::Ack(ack),
13118 ProtocolV2FetchAcknowledgment::Ready,
13119 ]),
13120 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13121 ];
13122 let mut encoded = Vec::new();
13123 write_protocol_v2_fetch_response(&mut encoded, §ions)
13124 .expect("test operation should succeed");
13125 encoded.extend_from_slice(b"tail");
13126
13127 let mut input = encoded.as_slice();
13128 assert_eq!(
13129 read_protocol_v2_fetch_response(ObjectFormat::Sha1, &mut input)
13130 .expect("test operation should succeed"),
13131 sections
13132 );
13133 assert_eq!(input, b"tail");
13134 }
13135
13136 #[test]
13137 fn protocol_v2_fetch_sideband_all_response_parses_sections_and_progress() {
13138 let frames = vec![
13139 PktLineFrame::Data(
13140 encode_sideband_packet(&SideBandPacket {
13141 channel: SideBandChannel::Data,
13142 data: b"acknowledgments\n".to_vec(),
13143 })
13144 .expect("test operation should succeed"),
13145 ),
13146 PktLineFrame::Data(
13147 encode_sideband_packet(&SideBandPacket {
13148 channel: SideBandChannel::Data,
13149 data: b"NAK\n".to_vec(),
13150 })
13151 .expect("test operation should succeed"),
13152 ),
13153 PktLineFrame::Data(
13154 encode_sideband_packet(&SideBandPacket {
13155 channel: SideBandChannel::Progress,
13156 data: b"keepalive\n".to_vec(),
13157 })
13158 .expect("test operation should succeed"),
13159 ),
13160 PktLineFrame::Delimiter,
13161 PktLineFrame::Data(
13162 encode_sideband_packet(&SideBandPacket {
13163 channel: SideBandChannel::Data,
13164 data: b"packfile\n".to_vec(),
13165 })
13166 .expect("test operation should succeed"),
13167 ),
13168 PktLineFrame::Data(b"\x01PACK".to_vec()),
13169 PktLineFrame::Data(b"\x02counting objects\n".to_vec()),
13170 PktLineFrame::Flush,
13171 ];
13172
13173 let response = parse_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &frames)
13174 .expect("test operation should succeed");
13175 assert_eq!(
13176 response,
13177 ProtocolV2FetchSidebandAllResponse {
13178 sections: vec![
13179 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13180 ProtocolV2FetchAcknowledgment::Nak
13181 ]),
13182 ProtocolV2FetchResponseSection::Packfile(vec![
13183 b"\x01PACK".to_vec(),
13184 b"\x02counting objects\n".to_vec(),
13185 ]),
13186 ],
13187 progress: vec![b"keepalive\n".to_vec()],
13188 }
13189 );
13190 assert_eq!(
13191 demux_protocol_v2_fetch_packfile(&response.sections)
13192 .expect("test operation should succeed"),
13193 Some(SideBandDemux {
13194 data: b"PACK".to_vec(),
13195 progress: vec![b"counting objects\n".to_vec()],
13196 })
13197 );
13198 }
13199
13200 #[test]
13201 fn protocol_v2_fetch_sideband_all_response_streams_round_trip() {
13202 let sections = vec![
13203 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13204 ProtocolV2FetchAcknowledgment::Nak,
13205 ]),
13206 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK bytes".to_vec()]),
13207 ];
13208 let mut encoded = Vec::new();
13209 write_protocol_v2_fetch_sideband_all_response(&mut encoded, §ions)
13210 .expect("test operation should succeed");
13211 encoded.extend_from_slice(b"tail");
13212
13213 let mut input = encoded.as_slice();
13214 assert_eq!(
13215 read_protocol_v2_fetch_sideband_all_response(ObjectFormat::Sha1, &mut input)
13216 .expect("test operation should succeed"),
13217 ProtocolV2FetchSidebandAllResponse {
13218 sections: sections.clone(),
13219 progress: Vec::new(),
13220 }
13221 );
13222 assert_eq!(input, b"tail");
13223
13224 let mut encoded = Vec::new();
13225 write_protocol_v2_fetch_sideband_all_response_with_response_end(&mut encoded, §ions)
13226 .expect("test operation should succeed");
13227 encoded.extend_from_slice(b"tail");
13228
13229 let mut input = encoded.as_slice();
13230 assert_eq!(
13231 read_protocol_v2_fetch_sideband_all_response_until_response_end(
13232 ObjectFormat::Sha1,
13233 &mut input,
13234 )
13235 .expect("test operation should succeed")
13236 .sections,
13237 sections
13238 );
13239 assert_eq!(input, b"tail");
13240 }
13241
13242 #[test]
13243 fn protocol_v2_fetch_sideband_all_response_rejects_malformed_sideband() {
13244 assert!(
13245 parse_protocol_v2_fetch_sideband_all_response(
13246 ObjectFormat::Sha1,
13247 &[
13248 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13249 PktLineFrame::Flush,
13250 ],
13251 )
13252 .is_err()
13253 );
13254 assert!(
13255 parse_protocol_v2_fetch_sideband_all_response(
13256 ObjectFormat::Sha1,
13257 &[
13258 PktLineFrame::Data(
13259 encode_sideband_packet(&SideBandPacket {
13260 channel: SideBandChannel::Fatal,
13261 data: b"remote died\n".to_vec(),
13262 })
13263 .expect("test operation should succeed"),
13264 ),
13265 PktLineFrame::Flush,
13266 ],
13267 )
13268 .is_err()
13269 );
13270 }
13271
13272 #[test]
13273 fn protocol_v2_object_info_response_parses_and_encodes_size_records() {
13274 let oid = ObjectId::from_hex(
13275 ObjectFormat::Sha1,
13276 "1111111111111111111111111111111111111111",
13277 )
13278 .expect("test operation should succeed");
13279 let frames = vec![
13280 PktLineFrame::Data(b"size\n".to_vec()),
13281 PktLineFrame::Data(b"1111111111111111111111111111111111111111 12345\n".to_vec()),
13282 PktLineFrame::Flush,
13283 ];
13284 let response = parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &frames)
13285 .expect("test operation should succeed");
13286 assert_eq!(
13287 response,
13288 ProtocolV2ObjectInfoResponse {
13289 size: true,
13290 records: vec![ProtocolV2ObjectInfoRecord { oid, size: 12345 }],
13291 }
13292 );
13293 assert_eq!(
13294 encode_protocol_v2_object_info_response(&response)
13295 .expect("test operation should succeed"),
13296 frames
13297 );
13298 }
13299
13300 #[test]
13301 fn protocol_v2_object_info_response_streams_and_exchanges() {
13302 let request = ProtocolV2ObjectInfoRequest {
13303 size: true,
13304 oids: vec![
13305 ObjectId::from_hex(
13306 ObjectFormat::Sha1,
13307 "1111111111111111111111111111111111111111",
13308 )
13309 .expect("test operation should succeed"),
13310 ],
13311 };
13312 let response = ProtocolV2ObjectInfoResponse {
13313 size: true,
13314 records: vec![ProtocolV2ObjectInfoRecord {
13315 oid: request.oids[0].clone(),
13316 size: 7,
13317 }],
13318 };
13319
13320 let mut encoded = Vec::new();
13321 write_protocol_v2_object_info_response(&mut encoded, &response)
13322 .expect("test operation should succeed");
13323 encoded.extend_from_slice(b"tail");
13324 let mut input = encoded.as_slice();
13325 assert_eq!(
13326 read_protocol_v2_object_info_response(ObjectFormat::Sha1, &mut input)
13327 .expect("test operation should succeed"),
13328 response
13329 );
13330 assert_eq!(input, b"tail");
13331
13332 let mut response_bytes = Vec::new();
13333 write_protocol_v2_object_info_response(&mut response_bytes, &response)
13334 .expect("test operation should succeed");
13335 let mut input = response_bytes.as_slice();
13336 let mut output = Vec::new();
13337 assert_eq!(
13338 exchange_protocol_v2_object_info(
13339 ObjectFormat::Sha1,
13340 &mut input,
13341 &mut output,
13342 &request,
13343 )
13344 .expect("test operation should succeed"),
13345 response
13346 );
13347 assert!(input.is_empty());
13348 let mut output_read = output.as_slice();
13349 assert_eq!(
13350 read_protocol_v2_object_info_request(ObjectFormat::Sha1, &mut output_read)
13351 .expect("test operation should succeed"),
13352 request
13353 );
13354 }
13355
13356 #[test]
13357 fn protocol_v2_object_info_response_rejects_malformed_records() {
13358 assert!(parse_protocol_v2_object_info_response(ObjectFormat::Sha1, &[]).is_err());
13359 assert!(
13360 parse_protocol_v2_object_info_response(
13361 ObjectFormat::Sha1,
13362 &[PktLineFrame::Data(b"size\n".to_vec())],
13363 )
13364 .is_err()
13365 );
13366 assert!(
13367 parse_protocol_v2_object_info_response(
13368 ObjectFormat::Sha1,
13369 &[PktLineFrame::Data(b"type\n".to_vec()), PktLineFrame::Flush,],
13370 )
13371 .is_err()
13372 );
13373 assert!(
13374 parse_protocol_v2_object_info_response(
13375 ObjectFormat::Sha1,
13376 &[
13377 PktLineFrame::Data(b"size\n".to_vec()),
13378 PktLineFrame::Data(
13379 b"1111111111111111111111111111111111111111 not-a-size\n".to_vec()
13380 ),
13381 PktLineFrame::Flush,
13382 ],
13383 )
13384 .is_err()
13385 );
13386 assert!(
13387 parse_protocol_v2_object_info_response(
13388 ObjectFormat::Sha1,
13389 &[
13390 PktLineFrame::Data(b"size\n".to_vec()),
13391 PktLineFrame::Delimiter,
13392 PktLineFrame::Flush,
13393 ],
13394 )
13395 .is_err()
13396 );
13397 assert!(
13398 encode_protocol_v2_object_info_response(&ProtocolV2ObjectInfoResponse {
13399 size: false,
13400 records: Vec::new(),
13401 })
13402 .is_err()
13403 );
13404 }
13405
13406 #[test]
13407 fn protocol_v2_fetch_response_reads_stateless_response_end() {
13408 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13409 ProtocolV2FetchAcknowledgment::Nak,
13410 ])];
13411 let mut encoded = Vec::new();
13412 write_protocol_v2_fetch_response_with_response_end(&mut encoded, §ions)
13413 .expect("test operation should succeed");
13414 encoded.extend_from_slice(b"tail");
13415
13416 let mut input = encoded.as_slice();
13417 assert_eq!(
13418 read_protocol_v2_fetch_response_until_response_end(ObjectFormat::Sha1, &mut input)
13419 .expect("test operation should succeed"),
13420 sections
13421 );
13422 assert_eq!(input, b"tail");
13423 assert!(
13424 parse_protocol_v2_fetch_response(
13425 ObjectFormat::Sha1,
13426 &[
13427 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13428 PktLineFrame::ResponseEnd,
13429 ],
13430 )
13431 .is_err()
13432 );
13433 }
13434
13435 #[test]
13436 fn protocol_v2_fetch_exchange_writes_request_and_reads_response() {
13437 let want = ObjectId::from_hex(
13438 ObjectFormat::Sha1,
13439 "1111111111111111111111111111111111111111",
13440 )
13441 .expect("test operation should succeed");
13442 let request = ProtocolV2FetchRequest {
13443 wants: vec![want],
13444 thin_pack: true,
13445 done: true,
13446 ..ProtocolV2FetchRequest::default()
13447 };
13448 let sections = vec![ProtocolV2FetchResponseSection::Acknowledgments(vec![
13449 ProtocolV2FetchAcknowledgment::Nak,
13450 ])];
13451 let mut response = Vec::new();
13452 write_protocol_v2_fetch_response(&mut response, §ions)
13453 .expect("test operation should succeed");
13454
13455 let mut input = response.as_slice();
13456 let mut output = Vec::new();
13457 assert_eq!(
13458 exchange_protocol_v2_fetch(ObjectFormat::Sha1, &mut input, &mut output, &request)
13459 .expect("test operation should succeed"),
13460 sections
13461 );
13462 assert!(input.is_empty());
13463 let mut output_read = output.as_slice();
13464 assert_eq!(
13465 read_protocol_v2_fetch_request(ObjectFormat::Sha1, &mut output_read)
13466 .expect("test operation should succeed"),
13467 request
13468 );
13469 }
13470
13471 #[test]
13472 fn protocol_v2_fetch_packfile_demuxes_sideband_section() {
13473 let sections = vec![
13474 ProtocolV2FetchResponseSection::Acknowledgments(vec![
13475 ProtocolV2FetchAcknowledgment::Nak,
13476 ]),
13477 ProtocolV2FetchResponseSection::Packfile(vec![
13478 b"\x01PACK".to_vec(),
13479 b"\x02counting objects\n".to_vec(),
13480 b"\x01 bytes".to_vec(),
13481 b"\x02done\n".to_vec(),
13482 ]),
13483 ];
13484
13485 assert_eq!(
13486 demux_protocol_v2_fetch_packfile(§ions).expect("test operation should succeed"),
13487 Some(SideBandDemux {
13488 data: b"PACK bytes".to_vec(),
13489 progress: vec![b"counting objects\n".to_vec(), b"done\n".to_vec()],
13490 })
13491 );
13492 assert_eq!(
13493 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Acknowledgments(
13494 vec![ProtocolV2FetchAcknowledgment::Nak],
13495 )])
13496 .expect("test operation should succeed"),
13497 None
13498 );
13499 }
13500
13501 #[test]
13502 fn protocol_v2_fetch_packfile_demux_rejects_duplicate_or_bad_sideband() {
13503 assert!(
13504 demux_protocol_v2_fetch_packfile(&[
13505 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01PACK".to_vec()]),
13506 ProtocolV2FetchResponseSection::Packfile(vec![b"\x01more".to_vec()]),
13507 ])
13508 .is_err()
13509 );
13510 assert!(
13511 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13512 b"\x03remote died\n".to_vec()
13513 ])])
13514 .is_err()
13515 );
13516 assert!(
13517 demux_protocol_v2_fetch_packfile(&[ProtocolV2FetchResponseSection::Packfile(vec![
13518 b"\x04bad".to_vec()
13519 ])])
13520 .is_err()
13521 );
13522 }
13523
13524 #[test]
13525 fn protocol_v2_fetch_response_rejects_malformed_sections() {
13526 assert!(
13527 parse_protocol_v2_fetch_response(
13528 ObjectFormat::Sha1,
13529 &[PktLineFrame::Data(b"acknowledgments\n".to_vec())],
13530 )
13531 .is_err()
13532 );
13533 assert!(
13534 parse_protocol_v2_fetch_response(
13535 ObjectFormat::Sha1,
13536 &[PktLineFrame::Delimiter, PktLineFrame::Flush],
13537 )
13538 .is_err()
13539 );
13540 assert!(
13541 parse_protocol_v2_fetch_response(
13542 ObjectFormat::Sha1,
13543 &[
13544 PktLineFrame::Data(b"acknowledgments\n".to_vec()),
13545 PktLineFrame::Data(b"ACK not-an-oid\n".to_vec()),
13546 PktLineFrame::Flush,
13547 ],
13548 )
13549 .is_err()
13550 );
13551 assert!(
13552 parse_protocol_v2_fetch_response(
13553 ObjectFormat::Sha1,
13554 &[
13555 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13556 PktLineFrame::Data(b"https://example.invalid/pack-a.pack\n".to_vec()),
13557 PktLineFrame::Flush,
13558 ],
13559 )
13560 .is_err()
13561 );
13562 assert!(
13563 parse_protocol_v2_fetch_response(
13564 ObjectFormat::Sha1,
13565 &[
13566 PktLineFrame::Data(b"packfile-uris\n".to_vec()),
13567 PktLineFrame::Data(
13568 b"not-a-hash https://example.invalid/pack-a.pack\n".to_vec()
13569 ),
13570 PktLineFrame::Flush,
13571 ],
13572 )
13573 .is_err()
13574 );
13575 assert!(
13576 encode_protocol_v2_fetch_response(&[ProtocolV2FetchResponseSection::WantedRefs(vec![
13577 ProtocolV2FetchWantedRef {
13578 oid: ObjectId::from_hex(
13579 ObjectFormat::Sha1,
13580 "1111111111111111111111111111111111111111",
13581 )
13582 .expect("test operation should succeed"),
13583 name: "bad ref".into(),
13584 }
13585 ])])
13586 .is_err()
13587 );
13588 }
13589
13590 #[test]
13591 fn protocol_v2_ls_refs_response_bridges_into_ref_advertisement_set() {
13592 let head = ObjectId::from_hex(
13593 ObjectFormat::Sha1,
13594 "1111111111111111111111111111111111111111",
13595 )
13596 .expect("test operation should succeed");
13597 let tag = ObjectId::from_hex(
13598 ObjectFormat::Sha1,
13599 "2222222222222222222222222222222222222222",
13600 )
13601 .expect("test operation should succeed");
13602 let tag_peeled = ObjectId::from_hex(
13603 ObjectFormat::Sha1,
13604 "3333333333333333333333333333333333333333",
13605 )
13606 .expect("test operation should succeed");
13607 let frames = vec![
13608 PktLineFrame::Data(
13609 b"1111111111111111111111111111111111111111 HEAD symref-target:refs/heads/main\n"
13610 .to_vec(),
13611 ),
13612 PktLineFrame::Data(
13613 b"1111111111111111111111111111111111111111 refs/heads/main\n".to_vec(),
13614 ),
13615 PktLineFrame::Data(
13616 b"2222222222222222222222222222222222222222 refs/tags/v1 peeled:3333333333333333333333333333333333333333\n"
13617 .to_vec(),
13618 ),
13619 PktLineFrame::Flush,
13620 ];
13621
13622 let set = parse_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13623 ObjectFormat::Sha1,
13624 &frames,
13625 )
13626 .expect("test operation should succeed");
13627 assert_eq!(
13628 set,
13629 RefAdvertisementSet {
13630 protocol: ProtocolVersion::V2,
13631 refs: vec![
13632 RefAdvertisement {
13633 oid: head.clone(),
13634 name: "HEAD".into(),
13635 capabilities: vec![Capability {
13636 name: "symref".into(),
13637 value: Some("HEAD:refs/heads/main".into()),
13638 }],
13639 },
13640 RefAdvertisement {
13641 oid: head,
13642 name: "refs/heads/main".into(),
13643 capabilities: Vec::new(),
13644 },
13645 RefAdvertisement {
13646 oid: tag,
13647 name: "refs/tags/v1".into(),
13648 capabilities: Vec::new(),
13649 },
13650 RefAdvertisement {
13651 oid: tag_peeled,
13652 name: "refs/tags/v1^{}".into(),
13653 capabilities: Vec::new(),
13654 },
13655 ],
13656 shallow: Vec::new(),
13657 }
13658 );
13659
13660 let mut encoded = Vec::new();
13662 write_pkt_line_frames(&mut encoded, &frames).expect("test operation should succeed");
13663 encoded.extend_from_slice(b"tail");
13664 let mut input = encoded.as_slice();
13665 assert_eq!(
13666 read_protocol_v2_ls_refs_response_as_ref_advertisement_set(
13667 ObjectFormat::Sha1,
13668 &mut input,
13669 )
13670 .expect("test operation should succeed"),
13671 set,
13672 );
13673 assert_eq!(input, b"tail");
13674 }
13675
13676 #[test]
13677 fn protocol_v2_ls_refs_records_bridge_unborn_head_symref_and_empty() {
13678 let records = vec![ProtocolV2LsRefsRecord::Unborn {
13681 name: "HEAD".into(),
13682 symref_target: Some("refs/heads/main".into()),
13683 attributes: Vec::new(),
13684 }];
13685 assert!(protocol_v2_ls_refs_records_to_ref_advertisement_set(&records).is_err());
13686
13687 assert_eq!(
13689 protocol_v2_ls_refs_records_to_ref_advertisement_set(&[])
13690 .expect("test operation should succeed"),
13691 RefAdvertisementSet {
13692 protocol: ProtocolVersion::V2,
13693 refs: Vec::new(),
13694 shallow: Vec::new(),
13695 }
13696 );
13697
13698 let main = ObjectId::from_hex(
13701 ObjectFormat::Sha1,
13702 "4444444444444444444444444444444444444444",
13703 )
13704 .expect("test operation should succeed");
13705 let records = vec![
13706 ProtocolV2LsRefsRecord::Unborn {
13707 name: "HEAD".into(),
13708 symref_target: Some("refs/heads/main".into()),
13709 attributes: Vec::new(),
13710 },
13711 ProtocolV2LsRefsRecord::Ref(ProtocolV2LsRefsRef {
13712 oid: main.clone(),
13713 name: "refs/heads/main".into(),
13714 peeled: None,
13715 symref_target: None,
13716 attributes: Vec::new(),
13717 }),
13718 ];
13719 let set = protocol_v2_ls_refs_records_to_ref_advertisement_set(&records)
13720 .expect("test operation should succeed");
13721 assert_eq!(
13722 set,
13723 RefAdvertisementSet {
13724 protocol: ProtocolVersion::V2,
13725 refs: vec![RefAdvertisement {
13726 oid: main,
13727 name: "refs/heads/main".into(),
13728 capabilities: vec![Capability {
13729 name: "symref".into(),
13730 value: Some("HEAD:refs/heads/main".into()),
13731 }],
13732 }],
13733 shallow: Vec::new(),
13734 }
13735 );
13736 }
13737}