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.deepen_since.as_deref().filter(|s| !s.trim().is_empty()) {
265 if server_caps.contains("deepen-since") {
266 let value = crate::shallow::deepen_since_wire_value(since);
267 pkt_line::write_line_to_vec(req, &format!("deepen-since {value}"))?;
268 }
269 }
270 if server_caps.contains("deepen-not") {
271 for excl in &opts.deepen_not {
272 let excl = excl.trim();
273 if !excl.is_empty() {
274 pkt_line::write_line_to_vec(req, &format!("deepen-not {excl}"))?;
275 }
276 }
277 }
278 Ok(())
279}
280
281fn negotiate_pack(
295 local_git_dir: &Path,
296 conn: &mut dyn Connection,
297 wants: &[ObjectId],
298 opts: &FetchOptions,
299 local_shallow: &[ObjectId],
300 progress: &mut dyn Progress,
301) -> Result<(Vec<u8>, ShallowUpdate)> {
302 let local_repo = crate::repo::Repository::open(local_git_dir, None)?;
303 let want_set: HashSet<ObjectId> = wants.iter().copied().collect();
304
305 let Some(first_want) = wants.first().copied() else {
306 return Ok((Vec::new(), ShallowUpdate::default()));
307 };
308
309 let shallow_request = opts.has_deepen_request() || !local_shallow.is_empty();
314
315 let caps = " multi_ack_detailed side-band-64k thin-pack no-progress include-tag ofs-delta agent=grit";
317
318 let advertised: Vec<(String, ObjectId)> = conn.advertised_refs().to_vec();
323 let server_caps: String = conn.capabilities().join(" ");
324
325 let mut req: Vec<u8> = Vec::new();
326 let w0 = format!("want {}{}", first_want.to_hex(), caps);
327 pkt_line::write_line_to_vec(&mut req, &w0)?;
328 for w in wants.iter().skip(1) {
329 pkt_line::write_line_to_vec(&mut req, &format!("want {}", w.to_hex()))?;
330 }
331 if wants.len() == 1 && !shallow_request {
335 pkt_line::write_line_to_vec(&mut req, &format!("want {}", first_want.to_hex()))?;
336 }
337 append_shallow_request_v0(&mut req, &server_caps, local_shallow, opts)?;
338 req.extend_from_slice(b"0000");
339 conn.writer().write_all(&req)?;
340 conn.writer().flush()?;
341
342 let mut negotiator = SkippingNegotiator::new(local_repo);
346 let mut tips: Vec<ObjectId> = Vec::new();
347 let mut seen_tip: HashSet<ObjectId> = HashSet::new();
348 for prefix in ["refs/heads/", "refs/tags/"] {
349 if let Ok(entries) = crate::refs::list_refs(local_git_dir, prefix) {
350 for (_, oid) in entries {
351 if let Some(c) = peel_to_commit(negotiator.repo(), oid) {
352 if !want_set.contains(&c) && seen_tip.insert(c) {
353 tips.push(c);
354 }
355 }
356 }
357 }
358 }
359 if let Ok(h) = crate::refs::resolve_ref(local_git_dir, "HEAD") {
360 if let Some(c) = peel_to_commit(negotiator.repo(), h) {
361 if !want_set.contains(&c) && seen_tip.insert(c) {
362 tips.push(c);
363 }
364 }
365 }
366 tips.sort_by_key(ObjectId::to_hex);
367 if !shallow_request {
368 for t in tips {
369 negotiator.add_tip(t)?;
370 }
371 for (_, oid) in &advertised {
372 if want_set.contains(oid) {
373 continue;
374 }
375 if let Some(c) = peel_to_commit(negotiator.repo(), *oid) {
376 negotiator.known_common(c)?;
377 }
378 }
379 }
380
381 let mut shallow_update = ShallowUpdate::default();
386 if shallow_request {
387 let (sh, unsh) = crate::shallow::read_shallow_info_section(&mut conn.reader())?;
388 shallow_update.shallow = sh;
389 shallow_update.unshallow = unsh;
390 }
391
392 let mut count: usize = 0;
394 let mut flush_at: usize = INITIAL_FLUSH;
395 let mut pending: Vec<u8> = Vec::new();
396 let mut flushes: i32 = 0;
397 while let Some(oid) = negotiator.next_have()? {
398 pkt_line::write_line_to_vec(&mut pending, &format!("have {}", oid.to_hex()))?;
399 count += 1;
400 if flush_at <= count {
401 pending.extend_from_slice(b"0000");
402 conn.writer().write_all(&pending)?;
403 conn.writer().flush()?;
404 pending.clear();
405 flush_at = next_flush_count(count);
406 flushes += 1;
407 if count == INITIAL_FLUSH {
409 continue;
410 }
411 read_ack_round(conn.reader(), &mut negotiator)?;
412 flushes -= 1;
413 }
414 }
415 if !pending.is_empty() {
416 pending.extend_from_slice(b"0000");
417 conn.writer().write_all(&pending)?;
418 conn.writer().flush()?;
419 flushes += 1;
420 }
421 while flushes > 0 {
422 read_ack_round(conn.reader(), &mut negotiator)?;
423 flushes -= 1;
424 }
425
426 let mut tail = Vec::new();
428 pkt_line::write_line_to_vec(&mut tail, "done")?;
429 conn.writer().write_all(&tail)?;
430 conn.writer().flush()?;
431
432 match pkt_line::read_packet(&mut conn.reader())? {
433 None => return Err(Error::Message("unexpected EOF after done".to_owned())),
434 Some(pkt_line::Packet::Flush) => {
435 return Err(Error::Message("unexpected flush after done".to_owned()))
436 }
437 Some(pkt_line::Packet::Data(ln)) => {
438 let ln = ln.trim_end();
439 if ln != "NAK" {
440 if let Some((ack_oid, kind)) = parse_ack(ln) {
441 if kind != AckKind::Bare {
442 let _ = negotiator.ack(ack_oid)?;
443 }
444 } else if let Some(msg) = ln.strip_prefix("ERR ") {
445 return Err(Error::Message(format!("remote error: {}", msg.trim_end())));
446 }
447 }
448 }
449 Some(_) => {}
450 }
451
452 let mut pack = Vec::new();
453 read_sideband_pack(conn.reader(), &mut pack, progress)?;
454 Ok((pack, shallow_update))
455}
456
457pub(crate) fn v2_object_format(server_caps: &[String], local_odb: &crate::odb::Odb) -> String {
472 for c in server_caps {
473 if let Some(fmt) = c.strip_prefix("object-format=") {
474 let f = fmt.trim();
475 if !f.is_empty() {
476 return f.to_ascii_lowercase();
477 }
478 }
479 }
480 local_odb.hash_algo().name().to_owned()
481}
482
483fn v2_ref_prefixes_from_refspecs(refspecs: &[String]) -> Vec<String> {
488 let mut out: Vec<String> = Vec::new();
489 let push_unique = |out: &mut Vec<String>, value: &str| {
490 if !out.iter().any(|v| v == value) {
491 out.push(value.to_owned());
492 }
493 };
494 for spec in refspecs {
495 if spec.starts_with('^') {
496 continue;
497 }
498 let raw = spec.strip_prefix('+').unwrap_or(spec.as_str());
499 let src = raw.split_once(':').map(|(s, _)| s).unwrap_or(raw).trim();
500 if src.is_empty() {
501 continue;
502 }
503 if src == "HEAD" {
504 push_unique(&mut out, "HEAD");
505 continue;
506 }
507 if let Some(star) = src.find('*') {
508 let prefix = &src[..star];
509 if prefix.is_empty() {
510 continue;
511 }
512 if prefix.starts_with("refs/") {
513 push_unique(&mut out, prefix);
514 } else {
515 push_unique(&mut out, &format!("refs/heads/{prefix}"));
516 }
517 continue;
518 }
519 if src.starts_with("refs/") {
520 push_unique(&mut out, src);
521 } else {
522 push_unique(&mut out, &format!("refs/heads/{src}"));
523 }
524 }
525 out
526}
527
528fn parse_ls_refs_v2_line(line: &str) -> Option<(String, ObjectId, Option<String>)> {
535 const SYM: &str = " symref-target:";
536 const PEEL: &str = " peeled:";
537 let (oid_hex, after_oid) = line.split_once(' ')?;
538 let oid = ObjectId::from_hex(oid_hex).ok()?;
539
540 let sym_at = after_oid.find(SYM);
542 let peel_at = after_oid.find(PEEL);
543 let name_end = match (sym_at, peel_at) {
544 (Some(a), Some(b)) => a.min(b),
545 (Some(a), None) => a,
546 (None, Some(b)) => b,
547 (None, None) => after_oid.len(),
548 };
549 let name = after_oid[..name_end].trim().to_owned();
550 if name.is_empty() {
551 return None;
552 }
553 let symref_target = sym_at.map(|pos| {
554 let tail = &after_oid[pos + SYM.len()..];
555 let end = tail.find(' ').unwrap_or(tail.len());
556 tail[..end].to_owned()
557 });
558 Some((name, oid, symref_target))
559}
560
561fn v2_ls_refs(
570 conn: &mut dyn Connection,
571 server_caps: &[String],
572 local_odb: &crate::odb::Odb,
573 tags: crate::transfer::TagMode,
574 refspecs: &[String],
575) -> Result<(Vec<(String, ObjectId)>, Option<String>)> {
576 let req = build_v2_ls_refs_request(server_caps, local_odb, tags, refspecs)?;
577 conn.writer().write_all(&req)?;
578 conn.writer().flush()?;
579 parse_v2_ls_refs_response(conn.reader())
580}
581
582pub(crate) fn build_v2_ls_refs_request(
591 server_caps: &[String],
592 local_odb: &crate::odb::Odb,
593 tags: crate::transfer::TagMode,
594 refspecs: &[String],
595) -> Result<Vec<u8>> {
596 let object_format = v2_object_format(server_caps, local_odb);
597 let cap_echo = protocol_v2::cap_lines_for_command_request(server_caps);
598
599 let mut req: Vec<u8> = Vec::new();
600 pkt_line::write_line(&mut req, "command=ls-refs")?;
601 if cap_echo.iter().any(|c| c.starts_with("object-format=")) {
604 for line in &cap_echo {
605 pkt_line::write_line(&mut req, line)?;
606 }
607 } else {
608 for line in &cap_echo {
609 pkt_line::write_line(&mut req, line)?;
610 }
611 pkt_line::write_line(&mut req, &format!("object-format={object_format}"))?;
612 }
613 pkt_line::write_delim(&mut req)?;
614 pkt_line::write_line(&mut req, "symrefs")?;
615 pkt_line::write_line(&mut req, "peel")?;
616
617 pkt_line::write_line(&mut req, "ref-prefix HEAD")?;
621 let mut prefixes = v2_ref_prefixes_from_refspecs(refspecs);
622 if prefixes.is_empty() {
623 prefixes.push("refs/heads/".to_owned());
624 prefixes.push("refs/tags/".to_owned());
625 } else if tags != crate::transfer::TagMode::None
626 && !prefixes.iter().any(|p| p == "refs/tags/")
627 {
628 prefixes.push("refs/tags/".to_owned());
631 }
632 for p in &prefixes {
633 pkt_line::write_line(&mut req, &format!("ref-prefix {p}"))?;
634 }
635 pkt_line::write_flush(&mut req)?;
636 Ok(req)
637}
638
639pub(crate) fn parse_v2_ls_refs_response(
645 reader: &mut dyn Read,
646) -> Result<(Vec<(String, ObjectId)>, Option<String>)> {
647 let mut advertised: Vec<(String, ObjectId)> = Vec::new();
649 let mut head_symref: Option<String> = None;
650 let mut reader = reader;
651 loop {
652 match pkt_line::read_packet(&mut reader)? {
653 None | Some(pkt_line::Packet::Flush) | Some(pkt_line::Packet::Delim) => break,
654 Some(pkt_line::Packet::ResponseEnd) => break,
655 Some(pkt_line::Packet::Data(line)) => {
656 let line = line.trim_end_matches('\n');
657 if let Some(msg) = line.strip_prefix("ERR ") {
658 return Err(Error::Message(format!(
659 "remote error: {}",
660 msg.trim_end()
661 )));
662 }
663 let Some((name, oid, symref_target)) = parse_ls_refs_v2_line(line) else {
664 continue;
665 };
666 if name.contains("^{") || name.ends_with("^{}") {
667 continue;
668 }
669 if name == "HEAD" {
670 if let Some(t) = symref_target {
671 head_symref = Some(t);
672 }
673 continue;
675 }
676 if name.starts_with("refs/heads/")
677 || name.starts_with("refs/tags/")
678 || name.starts_with("refs/")
679 {
680 advertised.push((name, oid));
681 }
682 }
683 }
684 }
685 Ok((advertised, head_symref))
686}
687
688pub(crate) fn v2_local_haves(
697 local_git_dir: &Path,
698 wants: &[ObjectId],
699) -> Result<Vec<ObjectId>> {
700 let want_set: HashSet<ObjectId> = wants.iter().copied().collect();
701 let local_repo = crate::repo::Repository::open(local_git_dir, None)?;
702 let mut negotiator = SkippingNegotiator::new(local_repo);
703 let mut tips: Vec<ObjectId> = Vec::new();
704 let mut seen_tip: HashSet<ObjectId> = HashSet::new();
705 for prefix in ["refs/heads/", "refs/tags/"] {
706 if let Ok(entries) = crate::refs::list_refs(local_git_dir, prefix) {
707 for (_, oid) in entries {
708 if let Some(c) = peel_to_commit(negotiator.repo(), oid) {
709 if !want_set.contains(&c) && seen_tip.insert(c) {
710 tips.push(c);
711 }
712 }
713 }
714 }
715 }
716 if let Ok(h) = crate::refs::resolve_ref(local_git_dir, "HEAD") {
717 if let Some(c) = peel_to_commit(negotiator.repo(), h) {
718 if !want_set.contains(&c) && seen_tip.insert(c) {
719 tips.push(c);
720 }
721 }
722 }
723 tips.sort_by_key(ObjectId::to_hex);
724 for t in tips {
725 negotiator.add_tip(t)?;
726 }
727 let mut haves: Vec<ObjectId> = Vec::new();
730 while let Some(oid) = negotiator.next_have()? {
731 haves.push(oid);
732 }
733 Ok(haves)
734}
735
736fn negotiate_pack_v2(
749 local_git_dir: &Path,
750 conn: &mut dyn Connection,
751 server_caps: &[String],
752 local_odb: &crate::odb::Odb,
753 wants: &[ObjectId],
754 deepen: &V2DeepenArgs,
755 progress: &mut dyn Progress,
756) -> Result<(Vec<u8>, ShallowUpdate)> {
757 if wants.is_empty() {
758 return Ok((Vec::new(), ShallowUpdate::default()));
759 }
760 let object_format = v2_object_format(server_caps, local_odb);
761 let cap_echo = protocol_v2::cap_lines_for_command_request(server_caps);
762 let sideband_all = protocol_v2::fetch_supports_sideband_all(server_caps);
763
764 let shallow_request = deepen.is_shallow_request();
768
769 let haves = if shallow_request {
773 Vec::new()
774 } else {
775 v2_local_haves(local_git_dir, wants)?
776 };
777
778 let mut pack = Vec::new();
779 let mut shallow_update = ShallowUpdate::default();
780 if haves.is_empty() {
781 write_v2_fetch_request(
783 conn.writer(),
784 &object_format,
785 &cap_echo,
786 wants,
787 &[],
788 sideband_all,
789 deepen,
790 true,
791 )?;
792 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
793 return Ok((pack, shallow_update));
794 }
795
796 let first_batch = haves.len().min(INITIAL_FLUSH);
798 write_v2_fetch_request(
799 conn.writer(),
800 &object_format,
801 &cap_echo,
802 wants,
803 &haves[..first_batch],
804 sideband_all,
805 deepen,
806 false,
807 )?;
808
809 let ack = read_v2_acknowledgments(conn.reader())?;
810 match ack {
811 Some(round) if round.ready => {
813 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
814 }
815 None => {
818 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
819 }
820 Some(_) => {
822 write_v2_fetch_request(
823 conn.writer(),
824 &object_format,
825 &cap_echo,
826 wants,
827 &haves[first_batch..],
828 sideband_all,
829 deepen,
830 true,
831 )?;
832 read_v2_fetch_pack_response(conn.reader(), &mut pack, &mut shallow_update, progress)?;
833 }
834 }
835 Ok((pack, shallow_update))
836}
837
838#[derive(Clone, Default)]
843pub(crate) struct V2DeepenArgs {
844 pub(crate) local_shallow: Vec<ObjectId>,
846 pub(crate) depth: Option<u32>,
848 pub(crate) deepen_since: Option<String>,
850 pub(crate) deepen_not: Vec<String>,
852}
853
854impl V2DeepenArgs {
855 pub(crate) fn from_opts(opts: &FetchOptions, local_shallow: &[ObjectId]) -> Self {
858 let depth = if opts.unshallow {
859 Some(crate::shallow::INFINITE_DEPTH)
860 } else {
861 opts.depth.filter(|d| *d > 0)
862 };
863 Self {
864 local_shallow: local_shallow.to_vec(),
865 depth,
866 deepen_since: opts
867 .deepen_since
868 .as_deref()
869 .filter(|s| !s.trim().is_empty())
870 .map(crate::shallow::deepen_since_wire_value),
871 deepen_not: opts
872 .deepen_not
873 .iter()
874 .map(|s| s.trim().to_owned())
875 .filter(|s| !s.is_empty())
876 .collect(),
877 }
878 }
879
880 pub(crate) fn is_shallow_request(&self) -> bool {
883 self.depth.is_some()
884 || self.deepen_since.is_some()
885 || !self.deepen_not.is_empty()
886 || !self.local_shallow.is_empty()
887 }
888}
889
890pub(crate) fn write_v2_fetch_request(
896 w: &mut dyn Write,
897 object_format: &str,
898 cap_echo: &[String],
899 wants: &[ObjectId],
900 haves: &[ObjectId],
901 sideband_all: bool,
902 deepen: &V2DeepenArgs,
903 send_done: bool,
904) -> Result<()> {
905 let mut req: Vec<u8> = Vec::new();
906 pkt_line::write_line(&mut req, "command=fetch")?;
907 if cap_echo.iter().any(|c| c.starts_with("object-format=")) {
908 for line in cap_echo {
909 pkt_line::write_line(&mut req, line)?;
910 }
911 } else {
912 for line in cap_echo {
913 pkt_line::write_line(&mut req, line)?;
914 }
915 pkt_line::write_line(&mut req, &format!("object-format={object_format}"))?;
916 }
917 pkt_line::write_delim(&mut req)?;
918
919 pkt_line::write_line(&mut req, "thin-pack")?;
920 pkt_line::write_line(&mut req, "no-progress")?;
921 pkt_line::write_line(&mut req, "ofs-delta")?;
922 if sideband_all {
923 pkt_line::write_line(&mut req, "sideband-all")?;
924 }
925 pkt_line::write_line(&mut req, "include-tag")?;
928
929 for oid in &deepen.local_shallow {
931 pkt_line::write_line(&mut req, &format!("shallow {}", oid.to_hex()))?;
932 }
933 if let Some(depth) = deepen.depth {
934 pkt_line::write_line(&mut req, &format!("deepen {depth}"))?;
935 }
936 if let Some(since) = &deepen.deepen_since {
937 pkt_line::write_line(&mut req, &format!("deepen-since {since}"))?;
938 }
939 for excl in &deepen.deepen_not {
940 pkt_line::write_line(&mut req, &format!("deepen-not {excl}"))?;
941 }
942
943 for want in wants {
944 pkt_line::write_line(&mut req, &format!("want {}", want.to_hex()))?;
945 }
946 for have in haves {
947 pkt_line::write_line(&mut req, &format!("have {}", have.to_hex()))?;
948 }
949 if send_done {
950 pkt_line::write_line(&mut req, "done")?;
951 }
952 pkt_line::write_flush(&mut req)?;
953 w.write_all(&req)?;
954 w.flush()?;
955 Ok(())
956}
957
958pub(crate) struct V2AckRound {
960 pub(crate) ready: bool,
963}
964
965pub(crate) fn read_v2_acknowledgments(reader: &mut dyn Read) -> Result<Option<V2AckRound>> {
973 let mut reader = reader;
974 let hdr = match pkt_line::read_packet(&mut reader)? {
975 Some(pkt_line::Packet::Data(s)) => s,
976 Some(pkt_line::Packet::Flush) => return Ok(Some(V2AckRound { ready: false })),
977 None => return Ok(None),
978 Some(other) => {
979 return Err(Error::Message(format!(
980 "unexpected v2 fetch response: {other:?}"
981 )))
982 }
983 };
984 let hdr = hdr.trim_end();
985 if let Some(msg) = hdr.strip_prefix("ERR ") {
986 return Err(Error::Message(format!("remote error: {}", msg.trim_end())));
987 }
988 if hdr != "acknowledgments" {
989 return Err(Error::Message(format!(
999 "unexpected v2 fetch section before acknowledgments: {hdr}"
1000 )));
1001 }
1002 let mut ready = false;
1003 loop {
1004 match pkt_line::read_packet(&mut reader)? {
1005 Some(pkt_line::Packet::Data(ln)) => {
1006 let ln = ln.trim_end();
1007 if ln == "NAK" || ln.starts_with("ACK ") {
1008 continue;
1009 }
1010 if ln == "ready" {
1011 ready = true;
1012 continue;
1013 }
1014 return Err(Error::Message(format!(
1015 "unexpected acknowledgment line: '{ln}'"
1016 )));
1017 }
1018 Some(pkt_line::Packet::Delim) | Some(pkt_line::Packet::Flush) | None => break,
1019 Some(other) => {
1020 return Err(Error::Message(format!(
1021 "unexpected acknowledgments packet: {other:?}"
1022 )))
1023 }
1024 }
1025 }
1026 Ok(Some(V2AckRound { ready }))
1027}
1028
1029pub(crate) fn read_v2_fetch_pack_response(
1035 reader: &mut dyn Read,
1036 out: &mut Vec<u8>,
1037 shallow_out: &mut ShallowUpdate,
1038 progress: &mut dyn Progress,
1039) -> Result<()> {
1040 loop {
1041 let hdr = match pkt_line::read_packet(&mut &mut *reader)? {
1042 Some(pkt_line::Packet::Data(s)) => s,
1043 Some(pkt_line::Packet::Flush) | None => return Ok(()),
1044 Some(pkt_line::Packet::Delim) => continue,
1045 Some(other) => {
1046 return Err(Error::Message(format!(
1047 "unexpected v2 fetch response: {other:?}"
1048 )))
1049 }
1050 };
1051 let hdr = hdr.trim_end();
1052 if let Some(msg) = hdr.strip_prefix("ERR ") {
1053 return Err(Error::Message(format!("remote error: {}", msg.trim_end())));
1054 }
1055 match hdr {
1056 "shallow-info" => {
1057 let (sh, unsh) = crate::shallow::read_shallow_info_section(&mut *reader)?;
1061 shallow_out.shallow.extend(sh);
1062 shallow_out.unshallow.extend(unsh);
1063 }
1064 "acknowledgments" | "wanted-refs" | "packfile-uris" => {
1065 skip_v2_section_until_boundary(&mut *reader)?;
1066 }
1067 "packfile" => {
1068 read_sideband_pack(&mut *reader, out, progress)?;
1071 return Ok(());
1072 }
1073 other => {
1074 return Err(Error::Message(format!(
1075 "unexpected v2 fetch section: {other}"
1076 )))
1077 }
1078 }
1079 }
1080}
1081
1082fn skip_v2_section_until_boundary(reader: &mut dyn Read) -> Result<()> {
1084 loop {
1085 match pkt_line::read_packet(&mut &mut *reader)? {
1086 None | Some(pkt_line::Packet::Flush) | Some(pkt_line::Packet::Delim) => return Ok(()),
1087 Some(pkt_line::Packet::ResponseEnd) => return Ok(()),
1088 Some(pkt_line::Packet::Data(_)) => {}
1089 }
1090 }
1091}
1092
1093pub fn fetch_remote(
1113 local_git_dir: &Path,
1114 conn: &mut dyn Connection,
1115 opts: &FetchOptions,
1116 progress: &mut dyn Progress,
1117) -> Result<FetchOutcome> {
1118 use crate::net_trace::net_trace;
1119 net_trace!(
1120 "fetch_remote: begin — protocol v{}, {} refspec(s), tags={:?}, depth={:?}",
1121 conn.protocol_version(),
1122 opts.refspecs.len(),
1123 opts.tags,
1124 opts.depth
1125 );
1126 let local_odb = open_odb(local_git_dir);
1127
1128 let (remote_refs, default_branch, v2_caps): (
1135 Vec<(String, ObjectId)>,
1136 Option<String>,
1137 Option<Vec<String>>,
1138 ) = if conn.protocol_version() >= 2 {
1139 let caps: Vec<String> = conn.capabilities().to_vec();
1140 let (refs, head_symref) =
1141 v2_ls_refs(conn, &caps, &local_odb, opts.tags, &opts.refspecs)?;
1142 let default_branch = head_symref.map(|t| {
1143 t.strip_prefix("refs/heads/")
1144 .unwrap_or(&t)
1145 .to_owned()
1146 });
1147 (refs, default_branch, Some(caps))
1148 } else {
1149 let default_branch = conn.head_symref().map(|t| {
1150 t.strip_prefix("refs/heads/")
1151 .unwrap_or(t)
1152 .to_owned()
1153 });
1154 let remote_refs: Vec<(String, ObjectId)> = conn
1155 .advertised_refs()
1156 .iter()
1157 .filter(|(n, _)| n != "HEAD" && !n.ends_with("^{}"))
1158 .cloned()
1159 .collect();
1160 (remote_refs, default_branch, None)
1161 };
1162 net_trace!(
1163 "fetch_remote: remote advertised {} ref(s){}",
1164 remote_refs.len(),
1165 v2_caps
1166 .as_ref()
1167 .map(|_| " (via v2 ls-refs)")
1168 .unwrap_or(" (v0/v1 advertisement)")
1169 );
1170
1171 let mut positive: Vec<RefspecItem> = Vec::new();
1173 let mut negatives: Vec<RefspecItem> = Vec::new();
1174 for spec in &opts.refspecs {
1175 let item = parse_fetch_refspec(spec)
1176 .map_err(|e| Error::Message(format!("invalid refspec '{spec}': {e}")))?;
1177 if item.negative {
1178 negatives.push(item);
1179 } else {
1180 positive.push(item);
1181 }
1182 }
1183 for spec in &opts.negative_refspecs {
1184 let item = parse_fetch_refspec(spec)
1185 .map_err(|e| Error::Message(format!("invalid negative refspec '{spec}': {e}")))?;
1186 negatives.push(item);
1187 }
1188
1189 let mut matched: Vec<crate::transfer::MatchedRef> = Vec::new();
1191 let mut matched_oids: HashSet<ObjectId> = HashSet::new();
1192 let mut seen_remote_ref: HashSet<String> = HashSet::new();
1193 for (name, oid) in &remote_refs {
1194 if ref_excluded(name, &negatives) {
1195 continue;
1196 }
1197 if let Some(local_ref) = match_positive(name, &positive) {
1198 if seen_remote_ref.insert(name.clone()) {
1199 matched_oids.insert(*oid);
1200 matched.push(crate::transfer::MatchedRef {
1201 remote_ref: name.clone(),
1202 local_ref,
1203 oid: *oid,
1204 force: refspecs_force(name, &positive),
1205 is_tag: name.starts_with("refs/tags/"),
1206 });
1207 }
1208 }
1209 }
1210
1211 let following_only = add_wire_tags(
1225 opts.tags,
1226 &remote_refs,
1227 &negatives,
1228 &mut matched,
1229 &mut matched_oids,
1230 &mut seen_remote_ref,
1231 );
1232
1233 let local_shallow = crate::shallow::load_shallow_oids(local_git_dir)?;
1236 let shallow_request = opts.has_deepen_request() || !local_shallow.is_empty();
1237
1238 let wants: Vec<ObjectId> = if shallow_request {
1243 matched_oids
1244 .iter()
1245 .copied()
1246 .filter(|oid| !following_only.contains(oid))
1247 .collect()
1248 } else {
1249 matched_oids
1250 .iter()
1251 .copied()
1252 .filter(|oid| !following_only.contains(oid) && !local_odb.exists(oid))
1253 .collect()
1254 };
1255
1256 let mut shallow_update = ShallowUpdate::default();
1259
1260 net_trace!(
1261 "fetch_remote: {} matched ref(s), want {} object(s){}",
1262 matched.len(),
1263 wants.len(),
1264 if shallow_request { " (shallow request)" } else { "" }
1265 );
1266
1267 if !wants.is_empty() && !opts.dry_run {
1268 net_trace!("fetch_remote: negotiating + fetching pack…");
1269 let (pack, su) = if let Some(caps) = v2_caps.as_ref() {
1270 let deepen = V2DeepenArgs::from_opts(opts, &local_shallow);
1271 negotiate_pack_v2(local_git_dir, conn, caps, &local_odb, &wants, &deepen, progress)?
1272 } else {
1273 negotiate_pack(local_git_dir, conn, &wants, opts, &local_shallow, progress)?
1274 };
1275 shallow_update = su;
1276 net_trace!(
1277 "fetch_remote: received pack ({} bytes), unpacking…",
1278 pack.len()
1279 );
1280 if !pack.is_empty() {
1281 let mut cursor = std::io::Cursor::new(pack);
1282 crate::unpack_objects::unpack_objects(
1283 &mut cursor,
1284 &local_odb,
1285 &crate::unpack_objects::UnpackOptions {
1286 quiet: true,
1287 ..Default::default()
1288 },
1289 )?;
1290 }
1291 }
1292
1293 if !opts.dry_run {
1296 crate::shallow::apply_shallow_updates(
1297 local_git_dir,
1298 &shallow_update.shallow,
1299 &shallow_update.unshallow,
1300 )?;
1301 }
1302
1303 if v2_caps.is_some() {
1309 conn.finish_send();
1310 }
1311
1312 if opts.tags == crate::transfer::TagMode::Following {
1316 retain_following_tags(&local_odb, &mut matched, &matched_oids);
1317 }
1318
1319 let local_repo = if opts.dry_run {
1321 None
1322 } else {
1323 crate::repo::Repository::open(local_git_dir, None).ok()
1324 };
1325
1326 let mut updates: Vec<RefUpdate> = Vec::new();
1327
1328 if opts.prune {
1329 prune_tracking_refs(
1330 local_git_dir,
1331 &positive,
1332 &remote_refs,
1333 opts.dry_run,
1334 &mut updates,
1335 )?;
1336 }
1337
1338 for m in &matched {
1339 let Some(local_ref) = &m.local_ref else {
1340 updates.push(RefUpdate {
1341 remote_ref: m.remote_ref.clone(),
1342 local_ref: None,
1343 old_oid: None,
1344 new_oid: Some(m.oid),
1345 mode: UpdateMode::NoChangeNeeded,
1346 note: Some("not stored (empty destination)".to_owned()),
1347 });
1348 continue;
1349 };
1350
1351 let old = crate::refs::resolve_ref(local_git_dir, local_ref).ok();
1352 let mode = classify_update(old.as_ref(), &m.oid, m.force, m.is_tag, local_repo.as_ref());
1353
1354 let write = matches!(
1355 mode,
1356 UpdateMode::New | UpdateMode::FastForward | UpdateMode::Forced
1357 );
1358 if write && !opts.dry_run {
1359 crate::refs::write_ref(local_git_dir, local_ref, &m.oid)?;
1360 }
1361
1362 updates.push(RefUpdate {
1363 remote_ref: m.remote_ref.clone(),
1364 local_ref: Some(local_ref.clone()),
1365 old_oid: old,
1366 new_oid: Some(m.oid),
1367 mode,
1368 note: None,
1369 });
1370 }
1371
1372 net_trace!(
1373 "fetch_remote: done — {} ref update(s){}",
1374 updates.len(),
1375 default_branch
1376 .as_deref()
1377 .map(|b| format!(", default branch '{b}'"))
1378 .unwrap_or_default()
1379 );
1380 Ok(FetchOutcome {
1381 updates,
1382 default_branch,
1383 new_shallow: shallow_update.shallow,
1384 new_unshallow: shallow_update.unshallow,
1385 })
1386}
1387
1388fn add_wire_tags(
1400 mode: crate::transfer::TagMode,
1401 remote_refs: &[(String, ObjectId)],
1402 negatives: &[RefspecItem],
1403 matched: &mut Vec<crate::transfer::MatchedRef>,
1404 matched_oids: &mut HashSet<ObjectId>,
1405 seen_remote_ref: &mut HashSet<String>,
1406) -> HashSet<ObjectId> {
1407 let mut following_only: HashSet<ObjectId> = HashSet::new();
1408 if mode == crate::transfer::TagMode::None {
1409 return following_only;
1410 }
1411 for (name, oid) in remote_refs {
1412 if !name.starts_with("refs/tags/") {
1413 continue;
1414 }
1415 if seen_remote_ref.contains(name) || ref_excluded(name, negatives) {
1416 continue;
1417 }
1418 seen_remote_ref.insert(name.clone());
1419 matched_oids.insert(*oid);
1420 if mode == crate::transfer::TagMode::Following {
1421 following_only.insert(*oid);
1422 }
1423 matched.push(crate::transfer::MatchedRef {
1424 remote_ref: name.clone(),
1425 local_ref: Some(name.clone()),
1426 oid: *oid,
1427 force: false,
1428 is_tag: true,
1429 });
1430 }
1431 following_only
1432}
1433
1434fn retain_following_tags(
1439 local_odb: &crate::odb::Odb,
1440 matched: &mut Vec<crate::transfer::MatchedRef>,
1441 matched_oids: &HashSet<ObjectId>,
1442) {
1443 let roots: Vec<ObjectId> = matched
1445 .iter()
1446 .filter(|m| !m.is_tag)
1447 .map(|m| m.oid)
1448 .collect();
1449 let closure = reachable_closure(local_odb, &roots);
1450 matched.retain(|m| {
1451 if !m.is_tag {
1452 return true;
1453 }
1454 let peeled = peel_tag_target(local_odb, m.oid);
1455 let have = local_odb.exists(&m.oid);
1458 have && (closure.contains(&m.oid)
1459 || closure.contains(&peeled)
1460 || matched_oids.contains(&peeled))
1461 });
1462}
1463
1464fn peel_tag_target(odb: &crate::odb::Odb, oid: ObjectId) -> ObjectId {
1466 let mut current = oid;
1467 for _ in 0..16 {
1468 let Ok(obj) = odb.read(¤t) else {
1469 return current;
1470 };
1471 if obj.kind != crate::objects::ObjectKind::Tag {
1472 return current;
1473 }
1474 match crate::objects::parse_tag(&obj.data) {
1475 Ok(t) => current = t.object,
1476 Err(_) => return current,
1477 }
1478 }
1479 current
1480}
1481
1482fn reachable_closure(odb: &crate::odb::Odb, roots: &[ObjectId]) -> HashSet<ObjectId> {
1486 use crate::objects::{parse_commit, parse_tag, parse_tree, ObjectKind};
1487
1488 let mut seen: HashSet<ObjectId> = HashSet::new();
1489 let mut stack: Vec<ObjectId> = roots.to_vec();
1490 while let Some(oid) = stack.pop() {
1491 if !seen.insert(oid) {
1492 continue;
1493 }
1494 let Ok(obj) = odb.read(&oid) else {
1495 continue;
1496 };
1497 match obj.kind {
1498 ObjectKind::Commit => {
1499 if let Ok(c) = parse_commit(&obj.data) {
1500 stack.push(c.tree);
1501 for p in c.parents {
1502 stack.push(p);
1503 }
1504 }
1505 }
1506 ObjectKind::Tree => {
1507 if let Ok(entries) = parse_tree(&obj.data) {
1508 for e in entries {
1509 stack.push(e.oid);
1510 }
1511 }
1512 }
1513 ObjectKind::Tag => {
1514 if let Ok(t) = parse_tag(&obj.data) {
1515 stack.push(t.object);
1516 }
1517 }
1518 ObjectKind::Blob => {}
1519 }
1520 }
1521 seen
1522}