1use std::collections::HashSet;
32use std::io::{Read, Write};
33use std::path::Path;
34
35use crate::error::{Error, Result};
36use crate::fetch_negotiator::SkippingNegotiator;
37use crate::objects::ObjectId;
38use crate::pkt_line;
39use crate::protocol_v2;
40use crate::refspec::{parse_fetch_refspec, RefspecItem};
41use crate::transfer::{
42 classify_update, match_positive, open_odb, prune_tracking_refs, ref_excluded, refspecs_force,
43 FetchOptions, FetchOutcome, RefUpdate, UpdateMode,
44};
45use crate::transport::Connection;
46
47pub trait Progress {
52 fn message(&mut self, _bytes: &[u8]) {}
54}
55
56pub struct NoProgress;
58
59impl Progress for NoProgress {}
60
61const INITIAL_FLUSH: usize = 16;
64const PIPESAFE_FLUSH: usize = 32;
65
66fn next_flush_count(count: usize) -> usize {
67 if count < PIPESAFE_FLUSH {
68 count * 2
69 } else {
70 count + PIPESAFE_FLUSH
71 }
72}
73
74#[derive(Clone, Copy, PartialEq, Eq)]
75enum AckKind {
76 Bare,
78 Common,
79 Continue,
80 Ready,
81}
82
83fn parse_ack(line: &str) -> Option<(ObjectId, AckKind)> {
84 if line == "NAK" {
85 return None;
86 }
87 let rest = line.strip_prefix("ACK ")?;
88 let hex = rest.split_whitespace().next()?;
89 let oid = ObjectId::from_hex(hex).ok()?;
90 let tail = rest.strip_prefix(hex).unwrap_or("").trim();
91 let kind = if tail.contains("continue") {
92 AckKind::Continue
93 } else if tail.contains("common") {
94 AckKind::Common
95 } else if tail.contains("ready") {
96 AckKind::Ready
97 } else {
98 AckKind::Bare
99 };
100 Some((oid, kind))
101}
102
103fn read_ack_round(reader: &mut dyn Read, negotiator: &mut SkippingNegotiator) -> Result<()> {
106 let mut reader = reader;
107 loop {
108 let Some(pkt) = pkt_line::read_packet(&mut reader)? else {
109 break;
110 };
111 match pkt {
112 pkt_line::Packet::Flush => break,
113 pkt_line::Packet::Data(ln) => {
114 let ln = ln.trim_end();
115 if ln == "NAK" {
116 break;
119 }
120 let Some((ack_oid, kind)) = parse_ack(ln) else {
121 break;
122 };
123 if kind == AckKind::Bare {
124 break;
125 }
126 let _ = negotiator.ack(ack_oid)?;
127 }
128 _ => {}
129 }
130 }
131 Ok(())
132}
133
134fn read_pkt_payload_raw(r: &mut dyn Read) -> std::io::Result<Option<Vec<u8>>> {
137 let mut len_buf = [0u8; 4];
138 match r.read_exact(&mut len_buf) {
139 Ok(()) => {}
140 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
141 Err(e) => return Err(e),
142 }
143 let len_str = std::str::from_utf8(&len_buf)
144 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
145 let len = usize::from_str_radix(len_str, 16)
146 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
147 match len {
148 0..=2 => Ok(None),
149 n if n <= 4 => Err(std::io::Error::new(
150 std::io::ErrorKind::InvalidData,
151 format!("invalid pkt-line length: {n}"),
152 )),
153 n => {
154 let payload_len = n - 4;
155 let mut buf = vec![0u8; payload_len];
156 r.read_exact(&mut buf)?;
157 Ok(Some(buf))
158 }
159 }
160}
161
162fn read_sideband_pack(
167 r: &mut dyn Read,
168 out: &mut Vec<u8>,
169 progress: &mut dyn Progress,
170) -> Result<()> {
171 let mut seen_pack = false;
172 let mut pending: Vec<u8> = Vec::new();
173 loop {
174 let Some(payload) = read_pkt_payload_raw(r)? else {
175 break;
176 };
177 if payload.is_empty() {
178 continue;
179 }
180 match payload[0] {
181 1 => {
182 let data = &payload[1..];
183 if seen_pack {
184 out.extend_from_slice(data);
185 } else {
186 pending.extend_from_slice(data);
187 if let Some(pos) = pending.windows(4).position(|w| w == b"PACK") {
188 seen_pack = true;
189 out.extend_from_slice(&pending[pos..]);
190 pending.clear();
191 } else if pending.len() > 3 {
192 let keep_from = pending.len() - 3;
193 pending.drain(..keep_from);
194 }
195 }
196 }
197 2 => progress.message(&payload[1..]),
198 3 => {
199 return Err(Error::Message(format!(
200 "remote error: {}",
201 String::from_utf8_lossy(&payload[1..]).trim_end()
202 )));
203 }
204 _ => {
205 if !seen_pack && payload.starts_with(b"PACK") {
207 seen_pack = true;
208 out.extend_from_slice(&payload);
209 } else if seen_pack {
210 out.extend_from_slice(&payload);
211 }
212 }
213 }
214 }
215 Ok(())
216}
217
218fn peel_to_commit(repo: &crate::repo::Repository, oid: ObjectId) -> Option<ObjectId> {
222 let mut current = oid;
223 for _ in 0..16 {
224 let obj = repo.odb.read(¤t).ok()?;
225 match obj.kind {
226 crate::objects::ObjectKind::Commit => return Some(current),
227 crate::objects::ObjectKind::Tag => {
228 current = crate::objects::parse_tag(&obj.data).ok()?.object;
229 }
230 _ => return None,
231 }
232 }
233 None
234}
235
236#[derive(Default)]
240pub(crate) struct ShallowUpdate {
241 pub(crate) shallow: Vec<ObjectId>,
242 pub(crate) unshallow: Vec<ObjectId>,
243}
244
245fn append_shallow_request_v0(
251 req: &mut Vec<u8>,
252 server_caps: &str,
253 local_shallow: &[ObjectId],
254 opts: &FetchOptions,
255) -> Result<()> {
256 for oid in local_shallow {
257 pkt_line::write_line_to_vec(req, &format!("shallow {}", oid.to_hex()))?;
258 }
259 if opts.unshallow {
260 pkt_line::write_line_to_vec(req, &format!("deepen {}", crate::shallow::INFINITE_DEPTH))?;
261 } else if let Some(depth) = opts.depth.filter(|d| *d > 0) {
262 pkt_line::write_line_to_vec(req, &format!("deepen {depth}"))?;
263 }
264 if let Some(since) = opts
265 .deepen_since
266 .as_deref()
267 .filter(|s| !s.trim().is_empty())
268 {
269 if server_caps.contains("deepen-since") {
270 let value = crate::shallow::deepen_since_wire_value(since);
271 pkt_line::write_line_to_vec(req, &format!("deepen-since {value}"))?;
272 }
273 }
274 if server_caps.contains("deepen-not") {
275 for excl in &opts.deepen_not {
276 let excl = excl.trim();
277 if !excl.is_empty() {
278 pkt_line::write_line_to_vec(req, &format!("deepen-not {excl}"))?;
279 }
280 }
281 }
282 Ok(())
283}
284
285fn negotiate_pack(
299 local_git_dir: &Path,
300 conn: &mut dyn Connection,
301 wants: &[ObjectId],
302 opts: &FetchOptions,
303 local_shallow: &[ObjectId],
304 progress: &mut dyn Progress,
305) -> Result<(Vec<u8>, ShallowUpdate)> {
306 let local_repo = crate::repo::Repository::open(local_git_dir, None)?;
307 let want_set: HashSet<ObjectId> = wants.iter().copied().collect();
308
309 let Some(first_want) = wants.first().copied() else {
310 return Ok((Vec::new(), ShallowUpdate::default()));
311 };
312
313 let shallow_request = opts.has_deepen_request() || !local_shallow.is_empty();
318
319 let caps =
321 " multi_ack_detailed side-band-64k thin-pack no-progress include-tag ofs-delta agent=grit";
322
323 let advertised: Vec<(String, ObjectId)> = conn.advertised_refs().to_vec();
328 let server_caps: String = conn.capabilities().join(" ");
329
330 let mut req: Vec<u8> = Vec::new();
331 let w0 = format!("want {}{}", first_want.to_hex(), caps);
332 pkt_line::write_line_to_vec(&mut req, &w0)?;
333 for w in wants.iter().skip(1) {
334 pkt_line::write_line_to_vec(&mut req, &format!("want {}", w.to_hex()))?;
335 }
336 if wants.len() == 1 && !shallow_request {
340 pkt_line::write_line_to_vec(&mut req, &format!("want {}", first_want.to_hex()))?;
341 }
342 append_shallow_request_v0(&mut req, &server_caps, local_shallow, opts)?;
343 req.extend_from_slice(b"0000");
344 conn.writer().write_all(&req)?;
345 conn.writer().flush()?;
346
347 let mut negotiator = SkippingNegotiator::new(local_repo);
351 let mut tips: Vec<ObjectId> = Vec::new();
352 let mut seen_tip: HashSet<ObjectId> = HashSet::new();
353 for prefix in ["refs/heads/", "refs/tags/"] {
354 if let Ok(entries) = crate::refs::list_refs(local_git_dir, prefix) {
355 for (_, oid) in entries {
356 if let Some(c) = peel_to_commit(negotiator.repo(), oid) {
357 if !want_set.contains(&c) && seen_tip.insert(c) {
358 tips.push(c);
359 }
360 }
361 }
362 }
363 }
364 if let Ok(h) = crate::refs::resolve_ref(local_git_dir, "HEAD") {
365 if let Some(c) = peel_to_commit(negotiator.repo(), h) {
366 if !want_set.contains(&c) && seen_tip.insert(c) {
367 tips.push(c);
368 }
369 }
370 }
371 tips.sort_by_key(ObjectId::to_hex);
372 if !shallow_request {
373 for t in tips {
374 negotiator.add_tip(t)?;
375 }
376 for (_, oid) in &advertised {
377 if want_set.contains(oid) {
378 continue;
379 }
380 if let Some(c) = peel_to_commit(negotiator.repo(), *oid) {
381 negotiator.known_common(c)?;
382 }
383 }
384 }
385
386 let mut shallow_update = ShallowUpdate::default();
391 if shallow_request {
392 let (sh, unsh) = crate::shallow::read_shallow_info_section(&mut conn.reader())?;
393 shallow_update.shallow = sh;
394 shallow_update.unshallow = unsh;
395 }
396
397 let mut count: usize = 0;
399 let mut flush_at: usize = INITIAL_FLUSH;
400 let mut pending: Vec<u8> = Vec::new();
401 let mut flushes: i32 = 0;
402 while let Some(oid) = negotiator.next_have()? {
403 pkt_line::write_line_to_vec(&mut pending, &format!("have {}", oid.to_hex()))?;
404 count += 1;
405 if flush_at <= count {
406 pending.extend_from_slice(b"0000");
407 conn.writer().write_all(&pending)?;
408 conn.writer().flush()?;
409 pending.clear();
410 flush_at = next_flush_count(count);
411 flushes += 1;
412 if count == INITIAL_FLUSH {
414 continue;
415 }
416 read_ack_round(conn.reader(), &mut negotiator)?;
417 flushes -= 1;
418 }
419 }
420 if !pending.is_empty() {
421 pending.extend_from_slice(b"0000");
422 conn.writer().write_all(&pending)?;
423 conn.writer().flush()?;
424 flushes += 1;
425 }
426 while flushes > 0 {
427 read_ack_round(conn.reader(), &mut negotiator)?;
428 flushes -= 1;
429 }
430
431 let mut tail = Vec::new();
433 pkt_line::write_line_to_vec(&mut tail, "done")?;
434 conn.writer().write_all(&tail)?;
435 conn.writer().flush()?;
436
437 match pkt_line::read_packet(&mut conn.reader())? {
438 None => return Err(Error::Message("unexpected EOF after done".to_owned())),
439 Some(pkt_line::Packet::Flush) => {
440 return Err(Error::Message("unexpected flush after done".to_owned()))
441 }
442 Some(pkt_line::Packet::Data(ln)) => {
443 let ln = ln.trim_end();
444 if ln != "NAK" {
445 if let Some((ack_oid, kind)) = parse_ack(ln) {
446 if kind != AckKind::Bare {
447 let _ = negotiator.ack(ack_oid)?;
448 }
449 } else if let Some(msg) = ln.strip_prefix("ERR ") {
450 return Err(Error::Message(format!("remote error: {}", msg.trim_end())));
451 }
452 }
453 }
454 Some(_) => {}
455 }
456
457 let mut pack = Vec::new();
458 read_sideband_pack(conn.reader(), &mut pack, progress)?;
459 Ok((pack, shallow_update))
460}
461
462pub(crate) fn v2_object_format(server_caps: &[String], local_odb: &crate::odb::Odb) -> String {
477 for c in server_caps {
478 if let Some(fmt) = c.strip_prefix("object-format=") {
479 let f = fmt.trim();
480 if !f.is_empty() {
481 return f.to_ascii_lowercase();
482 }
483 }
484 }
485 local_odb.hash_algo().name().to_owned()
486}
487
488fn v2_ref_prefixes_from_refspecs(refspecs: &[String]) -> Vec<String> {
493 let mut out: Vec<String> = Vec::new();
494 let push_unique = |out: &mut Vec<String>, value: &str| {
495 if !out.iter().any(|v| v == value) {
496 out.push(value.to_owned());
497 }
498 };
499 for spec in refspecs {
500 if spec.starts_with('^') {
501 continue;
502 }
503 let raw = spec.strip_prefix('+').unwrap_or(spec.as_str());
504 let src = raw.split_once(':').map(|(s, _)| s).unwrap_or(raw).trim();
505 if src.is_empty() {
506 continue;
507 }
508 if src == "HEAD" {
509 push_unique(&mut out, "HEAD");
510 continue;
511 }
512 if let Some(star) = src.find('*') {
513 let prefix = &src[..star];
514 if prefix.is_empty() {
515 continue;
516 }
517 if prefix.starts_with("refs/") {
518 push_unique(&mut out, prefix);
519 } else {
520 push_unique(&mut out, &format!("refs/heads/{prefix}"));
521 }
522 continue;
523 }
524 if src.starts_with("refs/") {
525 push_unique(&mut out, src);
526 } else {
527 push_unique(&mut out, &format!("refs/heads/{src}"));
528 }
529 }
530 out
531}
532
533fn parse_ls_refs_v2_line(line: &str) -> Option<(String, ObjectId, Option<String>)> {
540 const SYM: &str = " symref-target:";
541 const PEEL: &str = " peeled:";
542 let (oid_hex, after_oid) = line.split_once(' ')?;
543 let oid = ObjectId::from_hex(oid_hex).ok()?;
544
545 let sym_at = after_oid.find(SYM);
547 let peel_at = after_oid.find(PEEL);
548 let name_end = match (sym_at, peel_at) {
549 (Some(a), Some(b)) => a.min(b),
550 (Some(a), None) => a,
551 (None, Some(b)) => b,
552 (None, None) => after_oid.len(),
553 };
554 let name = after_oid[..name_end].trim().to_owned();
555 if name.is_empty() {
556 return None;
557 }
558 let symref_target = sym_at.map(|pos| {
559 let tail = &after_oid[pos + SYM.len()..];
560 let end = tail.find(' ').unwrap_or(tail.len());
561 tail[..end].to_owned()
562 });
563 Some((name, oid, symref_target))
564}
565
566fn v2_ls_refs(
575 conn: &mut dyn Connection,
576 server_caps: &[String],
577 local_odb: &crate::odb::Odb,
578 tags: crate::transfer::TagMode,
579 refspecs: &[String],
580) -> Result<(Vec<(String, ObjectId)>, Option<String>)> {
581 let req = build_v2_ls_refs_request(server_caps, local_odb, tags, refspecs)?;
582 conn.writer().write_all(&req)?;
583 conn.writer().flush()?;
584 parse_v2_ls_refs_response(conn.reader())
585}
586
587pub(crate) fn build_v2_ls_refs_request(
596 server_caps: &[String],
597 local_odb: &crate::odb::Odb,
598 tags: crate::transfer::TagMode,
599 refspecs: &[String],
600) -> Result<Vec<u8>> {
601 let object_format = v2_object_format(server_caps, local_odb);
602 let cap_echo = protocol_v2::cap_lines_for_command_request(server_caps);
603
604 let mut req: Vec<u8> = Vec::new();
605 pkt_line::write_line(&mut req, "command=ls-refs")?;
606 if cap_echo.iter().any(|c| c.starts_with("object-format=")) {
609 for line in &cap_echo {
610 pkt_line::write_line(&mut req, line)?;
611 }
612 } else {
613 for line in &cap_echo {
614 pkt_line::write_line(&mut req, line)?;
615 }
616 pkt_line::write_line(&mut req, &format!("object-format={object_format}"))?;
617 }
618 pkt_line::write_delim(&mut req)?;
619 pkt_line::write_line(&mut req, "symrefs")?;
620 pkt_line::write_line(&mut req, "peel")?;
621
622 pkt_line::write_line(&mut req, "ref-prefix HEAD")?;
626 let mut prefixes = v2_ref_prefixes_from_refspecs(refspecs);
627 if prefixes.is_empty() {
628 prefixes.push("refs/heads/".to_owned());
629 prefixes.push("refs/tags/".to_owned());
630 } else if tags != crate::transfer::TagMode::None && !prefixes.iter().any(|p| p == "refs/tags/")
631 {
632 prefixes.push("refs/tags/".to_owned());
635 }
636 for p in &prefixes {
637 pkt_line::write_line(&mut req, &format!("ref-prefix {p}"))?;
638 }
639 pkt_line::write_flush(&mut req)?;
640 Ok(req)
641}
642
643pub(crate) fn parse_v2_ls_refs_response(
649 reader: &mut dyn Read,
650) -> Result<(Vec<(String, ObjectId)>, Option<String>)> {
651 let mut advertised: Vec<(String, ObjectId)> = Vec::new();
653 let mut head_symref: Option<String> = None;
654 let mut reader = reader;
655 loop {
656 match pkt_line::read_packet(&mut reader)? {
657 None | Some(pkt_line::Packet::Flush) | Some(pkt_line::Packet::Delim) => break,
658 Some(pkt_line::Packet::ResponseEnd) => break,
659 Some(pkt_line::Packet::Data(line)) => {
660 let line = line.trim_end_matches('\n');
661 if let Some(msg) = line.strip_prefix("ERR ") {
662 return Err(Error::Message(format!("remote error: {}", msg.trim_end())));
663 }
664 let Some((name, oid, symref_target)) = parse_ls_refs_v2_line(line) else {
665 continue;
666 };
667 if name.contains("^{") || name.ends_with("^{}") {
668 continue;
669 }
670 if name == "HEAD" {
671 if let Some(t) = symref_target {
672 head_symref = Some(t);
673 }
674 continue;
676 }
677 if name.starts_with("refs/heads/")
678 || name.starts_with("refs/tags/")
679 || name.starts_with("refs/")
680 {
681 advertised.push((name, oid));
682 }
683 }
684 }
685 }
686 Ok((advertised, head_symref))
687}
688
689pub(crate) fn v2_local_haves(local_git_dir: &Path, wants: &[ObjectId]) -> Result<Vec<ObjectId>> {
698 let want_set: HashSet<ObjectId> = wants.iter().copied().collect();
699 let local_repo = crate::repo::Repository::open(local_git_dir, None)?;
700 let mut negotiator = SkippingNegotiator::new(local_repo);
701 let mut tips: Vec<ObjectId> = Vec::new();
702 let mut seen_tip: HashSet<ObjectId> = HashSet::new();
703 for prefix in ["refs/heads/", "refs/tags/"] {
704 if let Ok(entries) = crate::refs::list_refs(local_git_dir, prefix) {
705 for (_, oid) in entries {
706 if let Some(c) = peel_to_commit(negotiator.repo(), oid) {
707 if !want_set.contains(&c) && seen_tip.insert(c) {
708 tips.push(c);
709 }
710 }
711 }
712 }
713 }
714 if let Ok(h) = crate::refs::resolve_ref(local_git_dir, "HEAD") {
715 if let Some(c) = peel_to_commit(negotiator.repo(), h) {
716 if !want_set.contains(&c) && seen_tip.insert(c) {
717 tips.push(c);
718 }
719 }
720 }
721 tips.sort_by_key(ObjectId::to_hex);
722 for t in tips {
723 negotiator.add_tip(t)?;
724 }
725 let mut haves: Vec<ObjectId> = Vec::new();
728 while let Some(oid) = negotiator.next_have()? {
729 haves.push(oid);
730 }
731 Ok(haves)
732}
733
734fn negotiate_pack_v2(
747 local_git_dir: &Path,
748 conn: &mut dyn Connection,
749 server_caps: &[String],
750 local_odb: &crate::odb::Odb,
751 wants: &[ObjectId],
752 deepen: &V2DeepenArgs,
753 progress: &mut dyn Progress,
754) -> Result<(Vec<u8>, ShallowUpdate)> {
755 if wants.is_empty() {
756 return Ok((Vec::new(), ShallowUpdate::default()));
757 }
758 let object_format = v2_object_format(server_caps, local_odb);
759 let cap_echo = protocol_v2::cap_lines_for_command_request(server_caps);
760 let sideband_all = protocol_v2::fetch_supports_sideband_all(server_caps);
761
762 let shallow_request = deepen.is_shallow_request();
766
767 let haves = if shallow_request {
771 Vec::new()
772 } else {
773 v2_local_haves(local_git_dir, wants)?
774 };
775
776 let mut pack = Vec::new();
777 let mut shallow_update = ShallowUpdate::default();
778 if haves.is_empty() {
779 write_v2_fetch_request(
781 conn.writer(),
782 &object_format,
783 &cap_echo,
784 wants,
785 &[],
786 sideband_all,
787 deepen,
788 true,
789 )?;
790 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
791 return Ok((pack, shallow_update));
792 }
793
794 let first_batch = haves.len().min(INITIAL_FLUSH);
796 write_v2_fetch_request(
797 conn.writer(),
798 &object_format,
799 &cap_echo,
800 wants,
801 &haves[..first_batch],
802 sideband_all,
803 deepen,
804 false,
805 )?;
806
807 let ack = read_v2_acknowledgments(conn.reader())?;
808 match ack {
809 Some(round) if round.ready => {
811 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
812 }
813 None => {
816 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
817 }
818 Some(_) => {
820 write_v2_fetch_request(
821 conn.writer(),
822 &object_format,
823 &cap_echo,
824 wants,
825 &haves[first_batch..],
826 sideband_all,
827 deepen,
828 true,
829 )?;
830 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
831 }
832 }
833 Ok((pack, shallow_update))
834}
835
836#[derive(Clone, Default)]
841pub(crate) struct V2DeepenArgs {
842 pub(crate) local_shallow: Vec<ObjectId>,
844 pub(crate) depth: Option<u32>,
846 pub(crate) deepen_since: Option<String>,
848 pub(crate) deepen_not: Vec<String>,
850}
851
852impl V2DeepenArgs {
853 pub(crate) fn from_opts(opts: &FetchOptions, local_shallow: &[ObjectId]) -> Self {
856 let depth = if opts.unshallow {
857 Some(crate::shallow::INFINITE_DEPTH)
858 } else {
859 opts.depth.filter(|d| *d > 0)
860 };
861 Self {
862 local_shallow: local_shallow.to_vec(),
863 depth,
864 deepen_since: opts
865 .deepen_since
866 .as_deref()
867 .filter(|s| !s.trim().is_empty())
868 .map(crate::shallow::deepen_since_wire_value),
869 deepen_not: opts
870 .deepen_not
871 .iter()
872 .map(|s| s.trim().to_owned())
873 .filter(|s| !s.is_empty())
874 .collect(),
875 }
876 }
877
878 pub(crate) fn is_shallow_request(&self) -> bool {
881 self.depth.is_some()
882 || self.deepen_since.is_some()
883 || !self.deepen_not.is_empty()
884 || !self.local_shallow.is_empty()
885 }
886}
887
888pub(crate) fn write_v2_fetch_request(
894 w: &mut dyn Write,
895 object_format: &str,
896 cap_echo: &[String],
897 wants: &[ObjectId],
898 haves: &[ObjectId],
899 sideband_all: bool,
900 deepen: &V2DeepenArgs,
901 send_done: bool,
902) -> Result<()> {
903 let mut req: Vec<u8> = Vec::new();
904 pkt_line::write_line(&mut req, "command=fetch")?;
905 if cap_echo.iter().any(|c| c.starts_with("object-format=")) {
906 for line in cap_echo {
907 pkt_line::write_line(&mut req, line)?;
908 }
909 } else {
910 for line in cap_echo {
911 pkt_line::write_line(&mut req, line)?;
912 }
913 pkt_line::write_line(&mut req, &format!("object-format={object_format}"))?;
914 }
915 pkt_line::write_delim(&mut req)?;
916
917 pkt_line::write_line(&mut req, "thin-pack")?;
918 pkt_line::write_line(&mut req, "no-progress")?;
919 pkt_line::write_line(&mut req, "ofs-delta")?;
920 if sideband_all {
921 pkt_line::write_line(&mut req, "sideband-all")?;
922 }
923 pkt_line::write_line(&mut req, "include-tag")?;
926
927 for oid in &deepen.local_shallow {
929 pkt_line::write_line(&mut req, &format!("shallow {}", oid.to_hex()))?;
930 }
931 if let Some(depth) = deepen.depth {
932 pkt_line::write_line(&mut req, &format!("deepen {depth}"))?;
933 }
934 if let Some(since) = &deepen.deepen_since {
935 pkt_line::write_line(&mut req, &format!("deepen-since {since}"))?;
936 }
937 for excl in &deepen.deepen_not {
938 pkt_line::write_line(&mut req, &format!("deepen-not {excl}"))?;
939 }
940
941 for want in wants {
942 pkt_line::write_line(&mut req, &format!("want {}", want.to_hex()))?;
943 }
944 for have in haves {
945 pkt_line::write_line(&mut req, &format!("have {}", have.to_hex()))?;
946 }
947 if send_done {
948 pkt_line::write_line(&mut req, "done")?;
949 }
950 pkt_line::write_flush(&mut req)?;
951 w.write_all(&req)?;
952 w.flush()?;
953 Ok(())
954}
955
956pub(crate) struct V2AckRound {
958 pub(crate) ready: bool,
961}
962
963pub(crate) fn read_v2_acknowledgments(reader: &mut dyn Read) -> Result<Option<V2AckRound>> {
971 let mut reader = reader;
972 let hdr = match pkt_line::read_packet(&mut reader)? {
973 Some(pkt_line::Packet::Data(s)) => s,
974 Some(pkt_line::Packet::Flush) => return Ok(Some(V2AckRound { ready: false })),
975 None => return Ok(None),
976 Some(other) => {
977 return Err(Error::Message(format!(
978 "unexpected v2 fetch response: {other:?}"
979 )))
980 }
981 };
982 let hdr = hdr.trim_end();
983 if let Some(msg) = hdr.strip_prefix("ERR ") {
984 return Err(Error::Message(format!("remote error: {}", msg.trim_end())));
985 }
986 if hdr != "acknowledgments" {
987 return Err(Error::Message(format!(
997 "unexpected v2 fetch section before acknowledgments: {hdr}"
998 )));
999 }
1000 let mut ready = false;
1001 loop {
1002 match pkt_line::read_packet(&mut reader)? {
1003 Some(pkt_line::Packet::Data(ln)) => {
1004 let ln = ln.trim_end();
1005 if ln == "NAK" || ln.starts_with("ACK ") {
1006 continue;
1007 }
1008 if ln == "ready" {
1009 ready = true;
1010 continue;
1011 }
1012 return Err(Error::Message(format!(
1013 "unexpected acknowledgment line: '{ln}'"
1014 )));
1015 }
1016 Some(pkt_line::Packet::Delim) | Some(pkt_line::Packet::Flush) | None => break,
1017 Some(other) => {
1018 return Err(Error::Message(format!(
1019 "unexpected acknowledgments packet: {other:?}"
1020 )))
1021 }
1022 }
1023 }
1024 Ok(Some(V2AckRound { ready }))
1025}
1026
1027pub(crate) fn read_v2_fetch_pack_response(
1033 reader: &mut dyn Read,
1034 out: &mut Vec<u8>,
1035 shallow_out: &mut ShallowUpdate,
1036 progress: &mut dyn Progress,
1037) -> Result<()> {
1038 loop {
1039 let hdr = match pkt_line::read_packet(&mut &mut *reader)? {
1040 Some(pkt_line::Packet::Data(s)) => s,
1041 Some(pkt_line::Packet::Flush) | None => return Ok(()),
1042 Some(pkt_line::Packet::Delim) => continue,
1043 Some(other) => {
1044 return Err(Error::Message(format!(
1045 "unexpected v2 fetch response: {other:?}"
1046 )))
1047 }
1048 };
1049 let hdr = hdr.trim_end();
1050 if let Some(msg) = hdr.strip_prefix("ERR ") {
1051 return Err(Error::Message(format!("remote error: {}", msg.trim_end())));
1052 }
1053 match hdr {
1054 "shallow-info" => {
1055 let (sh, unsh) = crate::shallow::read_shallow_info_section(&mut *reader)?;
1059 shallow_out.shallow.extend(sh);
1060 shallow_out.unshallow.extend(unsh);
1061 }
1062 "acknowledgments" | "wanted-refs" | "packfile-uris" => {
1063 skip_v2_section_until_boundary(&mut *reader)?;
1064 }
1065 "packfile" => {
1066 read_sideband_pack(&mut *reader, out, progress)?;
1069 return Ok(());
1070 }
1071 other => {
1072 return Err(Error::Message(format!(
1073 "unexpected v2 fetch section: {other}"
1074 )))
1075 }
1076 }
1077 }
1078}
1079
1080fn skip_v2_section_until_boundary(reader: &mut dyn Read) -> Result<()> {
1082 loop {
1083 match pkt_line::read_packet(&mut &mut *reader)? {
1084 None | Some(pkt_line::Packet::Flush) | Some(pkt_line::Packet::Delim) => return Ok(()),
1085 Some(pkt_line::Packet::ResponseEnd) => return Ok(()),
1086 Some(pkt_line::Packet::Data(_)) => {}
1087 }
1088 }
1089}
1090
1091pub fn fetch_remote(
1111 local_git_dir: &Path,
1112 conn: &mut dyn Connection,
1113 opts: &FetchOptions,
1114 progress: &mut dyn Progress,
1115) -> Result<FetchOutcome> {
1116 use crate::net_trace::net_trace;
1117 net_trace!(
1118 "fetch_remote: begin — protocol v{}, {} refspec(s), tags={:?}, depth={:?}",
1119 conn.protocol_version(),
1120 opts.refspecs.len(),
1121 opts.tags,
1122 opts.depth
1123 );
1124 let local_odb = open_odb(local_git_dir);
1125
1126 let (remote_refs, default_branch, v2_caps): (
1133 Vec<(String, ObjectId)>,
1134 Option<String>,
1135 Option<Vec<String>>,
1136 ) = if conn.protocol_version() >= 2 {
1137 let caps: Vec<String> = conn.capabilities().to_vec();
1138 let (refs, head_symref) = v2_ls_refs(conn, &caps, &local_odb, opts.tags, &opts.refspecs)?;
1139 let default_branch =
1140 head_symref.map(|t| t.strip_prefix("refs/heads/").unwrap_or(&t).to_owned());
1141 (refs, default_branch, Some(caps))
1142 } else {
1143 let default_branch = conn
1144 .head_symref()
1145 .map(|t| t.strip_prefix("refs/heads/").unwrap_or(t).to_owned());
1146 let remote_refs: Vec<(String, ObjectId)> = conn
1147 .advertised_refs()
1148 .iter()
1149 .filter(|(n, _)| n != "HEAD" && !n.ends_with("^{}"))
1150 .cloned()
1151 .collect();
1152 (remote_refs, default_branch, None)
1153 };
1154 net_trace!(
1155 "fetch_remote: remote advertised {} ref(s){}",
1156 remote_refs.len(),
1157 v2_caps
1158 .as_ref()
1159 .map(|_| " (via v2 ls-refs)")
1160 .unwrap_or(" (v0/v1 advertisement)")
1161 );
1162
1163 let mut positive: Vec<RefspecItem> = Vec::new();
1165 let mut negatives: Vec<RefspecItem> = Vec::new();
1166 for spec in &opts.refspecs {
1167 let item = parse_fetch_refspec(spec)
1168 .map_err(|e| Error::Message(format!("invalid refspec '{spec}': {e}")))?;
1169 if item.negative {
1170 negatives.push(item);
1171 } else {
1172 positive.push(item);
1173 }
1174 }
1175 for spec in &opts.negative_refspecs {
1176 let item = parse_fetch_refspec(spec)
1177 .map_err(|e| Error::Message(format!("invalid negative refspec '{spec}': {e}")))?;
1178 negatives.push(item);
1179 }
1180
1181 let mut matched: Vec<crate::transfer::MatchedRef> = Vec::new();
1183 let mut matched_oids: HashSet<ObjectId> = HashSet::new();
1184 let mut seen_remote_ref: HashSet<String> = HashSet::new();
1185 for (name, oid) in &remote_refs {
1186 if ref_excluded(name, &negatives) {
1187 continue;
1188 }
1189 if let Some(local_ref) = match_positive(name, &positive) {
1190 if seen_remote_ref.insert(name.clone()) {
1191 matched_oids.insert(*oid);
1192 matched.push(crate::transfer::MatchedRef {
1193 remote_ref: name.clone(),
1194 local_ref,
1195 oid: *oid,
1196 force: refspecs_force(name, &positive),
1197 is_tag: name.starts_with("refs/tags/"),
1198 });
1199 }
1200 }
1201 }
1202
1203 let following_only = add_wire_tags(
1217 opts.tags,
1218 &remote_refs,
1219 &negatives,
1220 &mut matched,
1221 &mut matched_oids,
1222 &mut seen_remote_ref,
1223 );
1224
1225 let local_shallow = crate::shallow::load_shallow_oids(local_git_dir)?;
1228 let shallow_request = opts.has_deepen_request() || !local_shallow.is_empty();
1229
1230 let wants: Vec<ObjectId> = if shallow_request {
1235 matched_oids
1236 .iter()
1237 .copied()
1238 .filter(|oid| !following_only.contains(oid))
1239 .collect()
1240 } else {
1241 matched_oids
1242 .iter()
1243 .copied()
1244 .filter(|oid| !following_only.contains(oid) && !local_odb.exists(oid))
1245 .collect()
1246 };
1247
1248 let mut shallow_update = ShallowUpdate::default();
1251
1252 net_trace!(
1253 "fetch_remote: {} matched ref(s), want {} object(s){}",
1254 matched.len(),
1255 wants.len(),
1256 if shallow_request {
1257 " (shallow request)"
1258 } else {
1259 ""
1260 }
1261 );
1262
1263 if !wants.is_empty() && !opts.dry_run {
1264 net_trace!("fetch_remote: negotiating + fetching pack…");
1265 let (pack, su) = if let Some(caps) = v2_caps.as_ref() {
1266 let deepen = V2DeepenArgs::from_opts(opts, &local_shallow);
1267 negotiate_pack_v2(
1268 local_git_dir,
1269 conn,
1270 caps,
1271 &local_odb,
1272 &wants,
1273 &deepen,
1274 progress,
1275 )?
1276 } else {
1277 negotiate_pack(local_git_dir, conn, &wants, opts, &local_shallow, progress)?
1278 };
1279 shallow_update = su;
1280 net_trace!(
1281 "fetch_remote: received pack ({} bytes), unpacking…",
1282 pack.len()
1283 );
1284 if !pack.is_empty() {
1285 let mut cursor = std::io::Cursor::new(pack);
1286 crate::unpack_objects::unpack_objects(
1287 &mut cursor,
1288 &local_odb,
1289 &crate::unpack_objects::UnpackOptions {
1290 quiet: true,
1291 ..Default::default()
1292 },
1293 )?;
1294 }
1295 }
1296
1297 if !opts.dry_run {
1300 crate::shallow::apply_shallow_updates(
1301 local_git_dir,
1302 &shallow_update.shallow,
1303 &shallow_update.unshallow,
1304 )?;
1305 }
1306
1307 if v2_caps.is_some() {
1313 conn.finish_send();
1314 }
1315
1316 if opts.tags == crate::transfer::TagMode::Following {
1320 retain_following_tags(&local_odb, &mut matched, &matched_oids);
1321 }
1322
1323 let local_repo = if opts.dry_run {
1325 None
1326 } else {
1327 crate::repo::Repository::open(local_git_dir, None).ok()
1328 };
1329
1330 let mut updates: Vec<RefUpdate> = Vec::new();
1331
1332 if opts.prune {
1333 prune_tracking_refs(
1334 local_git_dir,
1335 &positive,
1336 &remote_refs,
1337 opts.dry_run,
1338 &mut updates,
1339 )?;
1340 }
1341
1342 for m in &matched {
1343 let Some(local_ref) = &m.local_ref else {
1344 updates.push(RefUpdate {
1345 remote_ref: m.remote_ref.clone(),
1346 local_ref: None,
1347 old_oid: None,
1348 new_oid: Some(m.oid),
1349 mode: UpdateMode::NoChangeNeeded,
1350 note: Some("not stored (empty destination)".to_owned()),
1351 });
1352 continue;
1353 };
1354
1355 let old = crate::refs::resolve_ref(local_git_dir, local_ref).ok();
1356 let mode = classify_update(old.as_ref(), &m.oid, m.force, m.is_tag, local_repo.as_ref());
1357
1358 let write = matches!(
1359 mode,
1360 UpdateMode::New | UpdateMode::FastForward | UpdateMode::Forced
1361 );
1362 if write && !opts.dry_run {
1363 crate::refs::write_ref(local_git_dir, local_ref, &m.oid)?;
1364 }
1365
1366 updates.push(RefUpdate {
1367 remote_ref: m.remote_ref.clone(),
1368 local_ref: Some(local_ref.clone()),
1369 old_oid: old,
1370 new_oid: Some(m.oid),
1371 mode,
1372 note: None,
1373 });
1374 }
1375
1376 net_trace!(
1377 "fetch_remote: done — {} ref update(s){}",
1378 updates.len(),
1379 default_branch
1380 .as_deref()
1381 .map(|b| format!(", default branch '{b}'"))
1382 .unwrap_or_default()
1383 );
1384 Ok(FetchOutcome {
1385 updates,
1386 default_branch,
1387 new_shallow: shallow_update.shallow,
1388 new_unshallow: shallow_update.unshallow,
1389 })
1390}
1391
1392fn add_wire_tags(
1404 mode: crate::transfer::TagMode,
1405 remote_refs: &[(String, ObjectId)],
1406 negatives: &[RefspecItem],
1407 matched: &mut Vec<crate::transfer::MatchedRef>,
1408 matched_oids: &mut HashSet<ObjectId>,
1409 seen_remote_ref: &mut HashSet<String>,
1410) -> HashSet<ObjectId> {
1411 let mut following_only: HashSet<ObjectId> = HashSet::new();
1412 if mode == crate::transfer::TagMode::None {
1413 return following_only;
1414 }
1415 for (name, oid) in remote_refs {
1416 if !name.starts_with("refs/tags/") {
1417 continue;
1418 }
1419 if seen_remote_ref.contains(name) || ref_excluded(name, negatives) {
1420 continue;
1421 }
1422 seen_remote_ref.insert(name.clone());
1423 matched_oids.insert(*oid);
1424 if mode == crate::transfer::TagMode::Following {
1425 following_only.insert(*oid);
1426 }
1427 matched.push(crate::transfer::MatchedRef {
1428 remote_ref: name.clone(),
1429 local_ref: Some(name.clone()),
1430 oid: *oid,
1431 force: false,
1432 is_tag: true,
1433 });
1434 }
1435 following_only
1436}
1437
1438fn retain_following_tags(
1443 local_odb: &crate::odb::Odb,
1444 matched: &mut Vec<crate::transfer::MatchedRef>,
1445 matched_oids: &HashSet<ObjectId>,
1446) {
1447 let roots: Vec<ObjectId> = matched
1449 .iter()
1450 .filter(|m| !m.is_tag)
1451 .map(|m| m.oid)
1452 .collect();
1453 let closure = reachable_closure(local_odb, &roots);
1454 matched.retain(|m| {
1455 if !m.is_tag {
1456 return true;
1457 }
1458 let peeled = peel_tag_target(local_odb, m.oid);
1459 let have = local_odb.exists(&m.oid);
1462 have && (closure.contains(&m.oid)
1463 || closure.contains(&peeled)
1464 || matched_oids.contains(&peeled))
1465 });
1466}
1467
1468fn peel_tag_target(odb: &crate::odb::Odb, oid: ObjectId) -> ObjectId {
1470 let mut current = oid;
1471 for _ in 0..16 {
1472 let Ok(obj) = odb.read(¤t) else {
1473 return current;
1474 };
1475 if obj.kind != crate::objects::ObjectKind::Tag {
1476 return current;
1477 }
1478 match crate::objects::parse_tag(&obj.data) {
1479 Ok(t) => current = t.object,
1480 Err(_) => return current,
1481 }
1482 }
1483 current
1484}
1485
1486fn reachable_closure(odb: &crate::odb::Odb, roots: &[ObjectId]) -> HashSet<ObjectId> {
1490 use crate::objects::{parse_commit, parse_tag, parse_tree, ObjectKind};
1491
1492 let mut seen: HashSet<ObjectId> = HashSet::new();
1493 let mut stack: Vec<ObjectId> = roots.to_vec();
1494 while let Some(oid) = stack.pop() {
1495 if !seen.insert(oid) {
1496 continue;
1497 }
1498 let Ok(obj) = odb.read(&oid) else {
1499 continue;
1500 };
1501 match obj.kind {
1502 ObjectKind::Commit => {
1503 if let Ok(c) = parse_commit(&obj.data) {
1504 stack.push(c.tree);
1505 for p in c.parents {
1506 stack.push(p);
1507 }
1508 }
1509 }
1510 ObjectKind::Tree => {
1511 if let Ok(entries) = parse_tree(&obj.data) {
1512 for e in entries {
1513 stack.push(e.oid);
1514 }
1515 }
1516 }
1517 ObjectKind::Tag => {
1518 if let Ok(t) = parse_tag(&obj.data) {
1519 stack.push(t.object);
1520 }
1521 }
1522 ObjectKind::Blob => {}
1523 }
1524 }
1525 seen
1526}