1use std::collections::{HashMap, HashSet, VecDeque};
13use std::path::Path;
14
15use sley_core::{Capability, GitError, ObjectFormat, ObjectId, Result};
16use sley_object::{Commit, ObjectType, Tag};
17use sley_odb::{
18 FileObjectDatabase, ObjectReader, RawPackInstallOptions, build_and_install_reachable_pack,
19 build_and_install_reachable_pack_filtered, build_reachable_pack, collect_reachable_object_ids,
20};
21use sley_protocol::{
22 PKT_LINE_MAX_PAYLOAD_LEN, ProtocolV2FetchShallowInfo, ReceivePackFeatures,
23 ReceivePackPushRequest, ReceivePackReportStatus, ReceivePackRequest, RefAdvertisement,
24 SideBandChannel, SideBandPacket, UploadPackFeatures, UploadPackNegotiationRequest,
25 UploadPackPackfileResponse, UploadPackRawPackfileResponse, UploadPackRequest,
26 apply_receive_pack_push_request, build_upload_pack_raw_packfile_response,
27 encode_receive_pack_features, encode_upload_pack_features,
28 read_upload_pack_negotiation_request, read_upload_pack_request,
29 write_upload_pack_negotiation_request, write_upload_pack_request,
30};
31use sley_refs::{DeleteRef, FileRefStore, Ref, RefPrecondition, RefTarget};
32
33fn zero_oid(format: ObjectFormat) -> Result<ObjectId> {
36 Ok(ObjectId::null(format))
37}
38
39fn resolve_for_each_ref_target(
42 store: &FileRefStore,
43 reference: &Ref,
44) -> Result<Option<(ObjectId, Option<String>)>> {
45 let mut target = reference.target.clone();
46 let mut symref = None;
47 for _ in 0..5 {
48 match target {
49 RefTarget::Direct(oid) => return Ok(Some((oid, symref))),
50 RefTarget::Symbolic(name) => {
51 symref.get_or_insert_with(|| name.clone());
52 let Some(next) = store.read_ref(&name)? else {
53 return Ok(None);
54 };
55 target = next;
56 }
57 }
58 }
59 Ok(None)
60}
61
62pub fn upload_pack_features(git_dir: &Path, format: ObjectFormat) -> Result<UploadPackFeatures> {
65 let store = FileRefStore::new(git_dir, format);
66 let mut symrefs = Vec::new();
67 if let Some(RefTarget::Symbolic(target)) = store.read_ref("HEAD")? {
68 symrefs.push(format!("HEAD:{target}"));
69 }
70 Ok(UploadPackFeatures {
71 object_format: Some(format),
72 side_band_64k: true,
73 symrefs,
74 ..UploadPackFeatures::default()
75 })
76}
77
78pub fn upload_pack_request_uses_sideband(request: &UploadPackRequest) -> bool {
80 request
81 .capabilities
82 .iter()
83 .any(|capability| matches!(capability.name.as_str(), "side-band" | "side-band-64k"))
84}
85
86pub fn upload_pack_sideband_response(
89 response: UploadPackRawPackfileResponse,
90) -> UploadPackPackfileResponse {
91 let mut sideband = Vec::new();
92 let chunk_len = PKT_LINE_MAX_PAYLOAD_LEN - 1;
93 for chunk in response.packfile.chunks(chunk_len) {
94 sideband.push(SideBandPacket {
95 channel: SideBandChannel::Data,
96 data: chunk.to_vec(),
97 });
98 }
99 UploadPackPackfileResponse {
100 acknowledgments: response.acknowledgments,
101 sideband,
102 }
103}
104
105pub fn attach_upload_pack_capabilities(
108 advertisements: &mut Vec<RefAdvertisement>,
109 format: ObjectFormat,
110 features: &UploadPackFeatures,
111) -> Result<()> {
112 let capabilities = encode_upload_pack_features(features)?;
113 if let Some(first) = advertisements.first_mut() {
114 first.capabilities = capabilities;
115 } else {
116 advertisements.push(RefAdvertisement {
117 oid: zero_oid(format)?,
118 name: "capabilities^{}".into(),
119 capabilities,
120 });
121 }
122 Ok(())
123}
124
125pub fn upload_pack_from_local_repository(
129 git_dir: &Path,
130 format: ObjectFormat,
131 features: &UploadPackFeatures,
132 request: UploadPackRequest,
133 haves: HashSet<ObjectId>,
134) -> Result<UploadPackRawPackfileResponse> {
135 let db = FileObjectDatabase::from_git_dir(git_dir, format);
136 build_upload_pack_raw_packfile_response(
137 features,
138 request,
139 haves,
140 |oid| db.contains(oid),
141 |wants, known_haves| {
142 let excluded = collect_reachable_object_ids(&db, format, known_haves)?;
143 build_reachable_pack(&db, format, wants, &excluded)
144 .map(|pack| pack.map(|pack| pack.pack))
145 },
146 )
147}
148
149pub fn receive_pack_features(format: ObjectFormat) -> ReceivePackFeatures {
152 ReceivePackFeatures {
153 report_status: true,
154 delete_refs: true,
155 ofs_delta: true,
156 push_options: true,
157 quiet: true,
158 object_format: Some(format),
159 ..ReceivePackFeatures::default()
160 }
161}
162
163pub fn receive_pack_request_uses_push_options(request: &ReceivePackRequest) -> bool {
166 request
167 .capabilities
168 .iter()
169 .any(|capability| capability.name == "push-options")
170}
171
172pub fn attach_receive_pack_capabilities(
175 advertisements: &mut Vec<RefAdvertisement>,
176 format: ObjectFormat,
177 features: &ReceivePackFeatures,
178) -> Result<()> {
179 let capabilities = encode_receive_pack_features(features)?;
180 if let Some(first) = advertisements.first_mut() {
181 first.capabilities = capabilities;
182 } else {
183 advertisements.push(RefAdvertisement {
184 oid: zero_oid(format)?,
185 name: "capabilities^{}".into(),
186 capabilities,
187 });
188 }
189 Ok(())
190}
191
192pub fn receive_pack_into_local_repository(
196 remote_git_dir: &Path,
197 format: ObjectFormat,
198 request: &ReceivePackPushRequest,
199) -> Result<ReceivePackReportStatus> {
200 let remote_store = FileRefStore::new(remote_git_dir, format);
201 let remote_db = FileObjectDatabase::from_git_dir(remote_git_dir, format);
202 apply_receive_pack_push_request(
203 &receive_pack_features(format),
204 request,
205 |name| match remote_store.read_ref(name)? {
206 Some(RefTarget::Direct(oid)) => Ok(Some(oid)),
207 Some(RefTarget::Symbolic(_)) | None => Ok(None),
208 },
209 |packfile| remote_db.install_raw_pack(packfile).map(|_| ()),
210 |oid| remote_db.contains(oid),
211 |commands| {
212 let mut tx = remote_store.transaction();
213 for command in commands {
214 let precondition = if command.old_id.is_null() {
215 RefPrecondition::MustNotExist
216 } else {
217 RefPrecondition::MustExistAndMatch(RefTarget::Direct(command.old_id))
218 };
219 tx.update_to(
220 command.name.clone(),
221 RefTarget::Direct(command.new_id),
222 precondition,
223 None,
224 );
225 }
226 tx.commit()
227 },
228 |command| {
229 remote_store
230 .delete_ref_checked(DeleteRef {
231 name: command.name.clone(),
232 expected_old: (!command.old_id.is_null()).then_some(command.old_id),
233 reflog: None,
234 })
235 .map(|_| ())
236 .map_err(|err| GitError::Transaction(err.to_string()))
237 },
238 )
239}
240
241pub fn receive_pack_reachable_pack_into_local_repository(
248 remote_git_dir: &Path,
249 format: ObjectFormat,
250 request: &ReceivePackPushRequest,
251 source_db: &FileObjectDatabase,
252 starts: Vec<ObjectId>,
253 excluded: HashSet<ObjectId>,
254) -> Result<ReceivePackReportStatus> {
255 let remote_store = FileRefStore::new(remote_git_dir, format);
256 let remote_db = FileObjectDatabase::from_git_dir(remote_git_dir, format);
257 let mut starts = Some(starts);
258 apply_receive_pack_push_request(
259 &receive_pack_features(format),
260 request,
261 |name| match remote_store.read_ref(name)? {
262 Some(RefTarget::Direct(oid)) => Ok(Some(oid)),
263 Some(RefTarget::Symbolic(_)) | None => Ok(None),
264 },
265 |_| {
266 let starts = starts.take().ok_or_else(|| {
267 GitError::InvalidFormat("receive-pack attempted to install pack twice".into())
268 })?;
269 build_and_install_reachable_pack(
270 source_db,
271 &remote_db,
272 format,
273 starts,
274 &excluded,
275 RawPackInstallOptions { promisor: false },
276 )?;
277 Ok(())
278 },
279 |oid| remote_db.contains(oid),
280 |commands| {
281 let mut tx = remote_store.transaction();
282 for command in commands {
283 let precondition = if command.old_id.is_null() {
284 RefPrecondition::MustNotExist
285 } else {
286 RefPrecondition::MustExistAndMatch(RefTarget::Direct(command.old_id))
287 };
288 tx.update_to(
289 command.name.clone(),
290 RefTarget::Direct(command.new_id),
291 precondition,
292 None,
293 );
294 }
295 tx.commit()
296 },
297 |command| {
298 remote_store
299 .delete_ref_checked(DeleteRef {
300 name: command.name.clone(),
301 expected_old: (!command.old_id.is_null()).then_some(command.old_id),
302 reflog: None,
303 })
304 .map(|_| ())
305 .map_err(|err| GitError::Transaction(err.to_string()))
306 },
307 )
308}
309
310pub fn local_fetch_advertisements(
313 git_dir: &Path,
314 format: ObjectFormat,
315) -> Result<Vec<RefAdvertisement>> {
316 let store = FileRefStore::new(git_dir, format);
317 let mut advertisements = Vec::new();
318 if let Some(target) = store.read_ref("HEAD")? {
319 let reference = Ref {
320 name: "HEAD".to_string(),
321 target,
322 };
323 if let Some((oid, _)) = resolve_for_each_ref_target(&store, &reference)? {
324 advertisements.push(RefAdvertisement {
325 oid,
326 name: reference.name,
327 capabilities: Vec::new(),
328 });
329 }
330 }
331 for reference in store.list_refs()? {
332 let Some((oid, _)) = resolve_for_each_ref_target(&store, &reference)? else {
333 continue;
334 };
335 advertisements.push(RefAdvertisement {
336 oid,
337 name: reference.name,
338 capabilities: Vec::new(),
339 });
340 }
341 Ok(advertisements)
342}
343
344pub fn local_have_oids(git_dir: &Path, format: ObjectFormat) -> Result<Vec<ObjectId>> {
347 let mut seen = HashSet::new();
348 let mut haves = Vec::new();
349 for advertisement in local_fetch_advertisements(git_dir, format)? {
350 if seen.insert(advertisement.oid) {
351 haves.push(advertisement.oid);
352 }
353 }
354 Ok(haves)
355}
356
357#[derive(Debug, Clone)]
364pub struct LocalDeepenPlan {
365 pub depth: u32,
369 pub deepen_since: bool,
371 pub deepen_not: usize,
373 pub client_shallow: Vec<ObjectId>,
376 pub shallow_info: Vec<ProtocolV2FetchShallowInfo>,
379 pub excluded: HashSet<ObjectId>,
384 pub extra_wants: Vec<ObjectId>,
388}
389
390fn peel_to_commit<R: ObjectReader>(
394 remote_db: &R,
395 format: ObjectFormat,
396 oid: &ObjectId,
397) -> Result<Option<ObjectId>> {
398 let mut oid = *oid;
399 loop {
400 let object = remote_db.read_object(&oid)?;
401 match object.object_type {
402 ObjectType::Commit => return Ok(Some(oid)),
403 ObjectType::Tag => oid = Tag::parse_ref(format, &object.body)?.object,
404 _ => return Ok(None),
405 }
406 }
407}
408
409pub fn compute_local_deepen<R: ObjectReader>(
422 remote_db: &R,
423 format: ObjectFormat,
424 heads: &[ObjectId],
425 client_shallow: Vec<ObjectId>,
426 depth: u32,
427 deepen_relative: bool,
428) -> Result<LocalDeepenPlan> {
429 let depth = if deepen_relative && depth < INFINITE_DEPTH {
432 depth.saturating_add(client_shallow_min_depth(
433 remote_db,
434 format,
435 heads,
436 &client_shallow,
437 )?)
438 } else {
439 depth
440 };
441 let mut min_depth: HashMap<ObjectId, u32> = HashMap::new();
442 let mut queue: VecDeque<ObjectId> = VecDeque::new();
443 for head in heads {
444 let Some(commit) = peel_to_commit(remote_db, format, head)? else {
445 continue;
446 };
447 if let std::collections::hash_map::Entry::Vacant(entry) = min_depth.entry(commit) {
448 entry.insert(0);
449 queue.push_back(commit);
450 }
451 }
452 let mut boundary = Vec::new();
457 let mut boundary_parents = HashSet::new();
458 while let Some(oid) = queue.pop_front() {
459 let commit_depth = min_depth[&oid];
460 let object = remote_db.read_object(&oid)?;
461 let parents = sley_odb::grafted_parents(
462 remote_db,
463 &oid,
464 Commit::parse_ref(format, &object.body)?.parents,
465 );
466 if (depth != INFINITE_DEPTH && commit_depth + 1 >= depth)
470 || remote_db.is_shallow_graft(&oid)
471 {
472 boundary.push(oid);
473 boundary_parents.extend(parents);
474 continue;
475 }
476 for parent in parents {
477 if let std::collections::hash_map::Entry::Vacant(entry) = min_depth.entry(parent) {
478 entry.insert(commit_depth + 1);
479 queue.push_back(parent);
480 }
481 }
482 }
483 let excluded = boundary_parents
487 .into_iter()
488 .filter(|parent| !min_depth.contains_key(parent))
489 .collect::<HashSet<_>>();
490
491 let client: HashSet<ObjectId> = client_shallow.iter().copied().collect();
492 let boundary_set: HashSet<ObjectId> = boundary.iter().copied().collect();
493 let mut shallow_info = Vec::new();
494 for oid in &boundary {
495 if !client.contains(oid) {
496 shallow_info.push(ProtocolV2FetchShallowInfo::Shallow(*oid));
497 }
498 }
499 let mut extra_wants = Vec::new();
500 for oid in &client_shallow {
501 let unshallowed = min_depth.contains_key(oid) && !boundary_set.contains(oid);
504 if !unshallowed {
505 continue;
506 }
507 shallow_info.push(ProtocolV2FetchShallowInfo::Unshallow(*oid));
508 let object = remote_db.read_object(oid)?;
509 extra_wants.extend(sley_odb::grafted_parents(
510 remote_db,
511 oid,
512 Commit::parse_ref(format, &object.body)?.parents,
513 ));
514 }
515 Ok(LocalDeepenPlan {
516 depth,
517 deepen_since: false,
518 deepen_not: 0,
519 client_shallow,
520 shallow_info,
521 excluded,
522 extra_wants,
523 })
524}
525
526pub const INFINITE_DEPTH: u32 = 0x7fff_ffff;
529
530fn client_shallow_min_depth<R: ObjectReader>(
534 remote_db: &R,
535 format: ObjectFormat,
536 heads: &[ObjectId],
537 client_shallow: &[ObjectId],
538) -> Result<u32> {
539 if client_shallow.is_empty() {
540 return Ok(0);
541 }
542 let client: HashSet<ObjectId> = client_shallow.iter().copied().collect();
543 let mut min_depth: HashMap<ObjectId, u32> = HashMap::new();
544 let mut queue: VecDeque<ObjectId> = VecDeque::new();
545 for head in heads {
546 let Some(commit) = peel_to_commit(remote_db, format, head)? else {
547 continue;
548 };
549 if let std::collections::hash_map::Entry::Vacant(entry) = min_depth.entry(commit) {
550 entry.insert(1);
551 queue.push_back(commit);
552 }
553 }
554 let mut best: u32 = 0;
555 while let Some(oid) = queue.pop_front() {
556 let commit_depth = min_depth[&oid];
557 if client.contains(&oid) && (best == 0 || commit_depth < best) {
558 best = commit_depth;
559 }
560 let object = remote_db.read_object(&oid)?;
561 let parents = sley_odb::grafted_parents(
562 remote_db,
563 &oid,
564 Commit::parse_ref(format, &object.body)?.parents,
565 );
566 for parent in parents {
567 if let std::collections::hash_map::Entry::Vacant(entry) = min_depth.entry(parent) {
568 entry.insert(commit_depth + 1);
569 queue.push_back(parent);
570 }
571 }
572 }
573 Ok(best)
574}
575
576pub fn compute_local_deepen_by_rev_list<R: ObjectReader>(
582 remote_db: &R,
583 format: ObjectFormat,
584 heads: &[ObjectId],
585 client_shallow: Vec<ObjectId>,
586 since: Option<i64>,
587 deepen_not: &[ObjectId],
588) -> Result<LocalDeepenPlan> {
589 let mut excluded_not: HashSet<ObjectId> = HashSet::new();
591 let mut queue: VecDeque<ObjectId> = VecDeque::new();
592 for tip in deepen_not {
593 if let Some(commit) = peel_to_commit(remote_db, format, tip)?
594 && excluded_not.insert(commit)
595 {
596 queue.push_back(commit);
597 }
598 }
599 while let Some(oid) = queue.pop_front() {
600 let object = remote_db.read_object(&oid)?;
601 for parent in sley_odb::grafted_parents(
602 remote_db,
603 &oid,
604 Commit::parse_ref(format, &object.body)?.parents,
605 ) {
606 if excluded_not.insert(parent) {
607 queue.push_back(parent);
608 }
609 }
610 }
611
612 let commit_time = |oid: &ObjectId| -> Result<i64> {
613 let object = remote_db.read_object(oid)?;
614 Ok(Commit::parse_ref(format, &object.body)?
615 .committer_signature()
616 .map(|signature| signature.time.seconds)
617 .unwrap_or(0))
618 };
619 let keeps = |oid: &ObjectId| -> Result<bool> {
620 if excluded_not.contains(oid) {
621 return Ok(false);
622 }
623 match since {
624 Some(since) => Ok(commit_time(oid)? >= since),
625 None => Ok(true),
626 }
627 };
628
629 let mut kept: HashSet<ObjectId> = HashSet::new();
632 let mut kept_order: Vec<ObjectId> = Vec::new();
633 let mut queue: VecDeque<ObjectId> = VecDeque::new();
634 for head in heads {
635 let Some(commit) = peel_to_commit(remote_db, format, head)? else {
636 continue;
637 };
638 if keeps(&commit)? && kept.insert(commit) {
639 kept_order.push(commit);
640 queue.push_back(commit);
641 }
642 }
643 while let Some(oid) = queue.pop_front() {
644 let object = remote_db.read_object(&oid)?;
645 for parent in sley_odb::grafted_parents(
646 remote_db,
647 &oid,
648 Commit::parse_ref(format, &object.body)?.parents,
649 ) {
650 if !kept.contains(&parent) && keeps(&parent)? {
651 kept.insert(parent);
652 kept_order.push(parent);
653 queue.push_back(parent);
654 }
655 }
656 }
657 if kept.is_empty() {
658 return Err(GitError::Command(
660 "no commits selected for shallow requests".into(),
661 ));
662 }
663
664 let mut boundary = Vec::new();
666 let mut boundary_set: HashSet<ObjectId> = HashSet::new();
667 let mut excluded: HashSet<ObjectId> = HashSet::new();
668 for oid in &kept_order {
669 let object = remote_db.read_object(oid)?;
670 let parents = sley_odb::grafted_parents(
671 remote_db,
672 oid,
673 Commit::parse_ref(format, &object.body)?.parents,
674 );
675 let mut is_boundary = false;
676 for parent in parents {
677 if !kept.contains(&parent) {
678 is_boundary = true;
679 excluded.insert(parent);
680 }
681 }
682 if is_boundary && boundary_set.insert(*oid) {
683 boundary.push(*oid);
684 }
685 }
686
687 let client: HashSet<ObjectId> = client_shallow.iter().copied().collect();
688 let mut shallow_info = Vec::new();
689 for oid in &boundary {
690 if !client.contains(oid) {
691 shallow_info.push(ProtocolV2FetchShallowInfo::Shallow(*oid));
692 }
693 }
694 let mut extra_wants = Vec::new();
695 for oid in &client_shallow {
696 let unshallowed = kept.contains(oid) && !boundary_set.contains(oid);
697 if !unshallowed {
698 continue;
699 }
700 shallow_info.push(ProtocolV2FetchShallowInfo::Unshallow(*oid));
701 let object = remote_db.read_object(oid)?;
702 extra_wants.extend(sley_odb::grafted_parents(
703 remote_db,
704 oid,
705 Commit::parse_ref(format, &object.body)?.parents,
706 ));
707 }
708 Ok(LocalDeepenPlan {
709 depth: 0,
710 deepen_since: since.is_some(),
711 deepen_not: deepen_not.len(),
712 client_shallow,
713 shallow_info,
714 excluded,
715 extra_wants,
716 })
717}
718
719#[allow(clippy::too_many_arguments)]
732pub fn install_fetch_pack_via_local_upload_pack(
733 git_dir: &Path,
734 remote_git_dir: &Path,
735 format: ObjectFormat,
736 wants: Vec<ObjectId>,
737 deepen: Option<&LocalDeepenPlan>,
738 promisor: bool,
739 filter: Option<sley_odb::PackObjectFilter>,
740 unpack_limit: Option<usize>,
741) -> Result<Vec<ProtocolV2FetchShallowInfo>> {
742 if wants.is_empty() {
743 return Ok(Vec::new());
744 }
745 let local_db = FileObjectDatabase::from_git_dir(git_dir, format);
746 if deepen.is_none()
749 && wants
750 .iter()
751 .map(|want| local_db.contains(want))
752 .collect::<Result<Vec<_>>>()?
753 .into_iter()
754 .all(|contains| contains)
755 {
756 return Ok(Vec::new());
757 }
758
759 let request = UploadPackRequest {
760 wants,
761 capabilities: deepen
764 .map(|_| {
765 vec![Capability {
766 name: "shallow".into(),
767 value: None,
768 }]
769 })
770 .unwrap_or_default(),
771 shallow: deepen
772 .map(|plan| plan.client_shallow.clone())
773 .unwrap_or_default(),
774 deepen: deepen.and_then(|plan| (plan.depth > 0).then_some(plan.depth)),
775 ..UploadPackRequest::default()
776 };
777 let mut encoded_request = Vec::new();
778 write_upload_pack_request(&mut encoded_request, Some(&request))?;
779 let decoded_request = read_upload_pack_request(format, &mut encoded_request.as_slice())?
780 .ok_or_else(|| GitError::InvalidFormat("encoded upload-pack request was empty".into()))?;
781
782 let haves = local_have_oids(git_dir, format)?;
783 let negotiation = UploadPackNegotiationRequest { haves, done: true };
784 let mut encoded_negotiation = Vec::new();
785 write_upload_pack_negotiation_request(&mut encoded_negotiation, &negotiation)?;
786 let decoded_negotiation =
787 read_upload_pack_negotiation_request(format, &mut encoded_negotiation.as_slice())?;
788
789 let remote_db = FileObjectDatabase::from_git_dir(remote_git_dir, format);
790 for want in &decoded_request.wants {
791 if !remote_db.contains(want)? {
792 return Err(GitError::InvalidObject(format!(
793 "upload-pack requested missing object {want}"
794 )));
795 }
796 }
797 let known_haves = decoded_negotiation
798 .haves
799 .into_iter()
800 .filter_map(|oid| match remote_db.contains(&oid) {
801 Ok(true) => Some(Ok(oid)),
802 Ok(false) => None,
803 Err(err) => Some(Err(err)),
804 })
805 .collect::<Result<Vec<_>>>()?;
806 trace2_fetch_info(
810 known_haves.len(),
811 decoded_request.wants.len(),
812 deepen.map(|plan| plan.depth).unwrap_or(0),
813 deepen.map(|plan| plan.client_shallow.len()).unwrap_or(0),
814 deepen.is_some_and(|plan| plan.deepen_since),
815 deepen.map(|plan| plan.deepen_not).unwrap_or(0),
816 filter,
817 );
818 let mut excluded = match deepen {
823 Some(plan) => {
824 let cut: HashSet<ObjectId> = plan.client_shallow.iter().copied().collect();
825 sley_odb::collect_reachable_object_ids_with_cut(&remote_db, format, known_haves, &cut)?
826 }
827 None => collect_reachable_object_ids(&remote_db, format, known_haves)?,
828 };
829 let mut starts = decoded_request.wants;
830 if let Some(plan) = deepen {
831 excluded.extend(plan.excluded.iter().copied());
834 starts.extend(plan.extra_wants.iter().copied());
835 }
836 build_and_install_reachable_pack_filtered(
837 &remote_db,
838 &local_db,
839 format,
840 starts,
841 &excluded,
842 RawPackInstallOptions { promisor },
843 filter,
844 unpack_limit,
845 )?;
846 Ok(deepen
847 .map(|plan| plan.shallow_info.clone())
848 .unwrap_or_default())
849}
850
851fn trace2_fetch_info(
855 haves: usize,
856 wants: usize,
857 depth: u32,
858 shallows: usize,
859 deepen_since: bool,
860 deepen_not: usize,
861 filter: Option<sley_odb::PackObjectFilter>,
862) {
863 let Some(path) = std::env::var_os("GIT_TRACE2_EVENT") else {
864 return;
865 };
866 if path.is_empty() {
867 return;
868 }
869 let filter_json = match filter {
870 Some(sley_odb::PackObjectFilter::BlobNone) => "\"blob:none\"".to_string(),
871 None => "null".to_string(),
872 };
873 let line = format!(
874 "{{\"event\":\"data_json\",\"thread\":\"main\",\"category\":\"upload-pack\",\"key\":\"fetch-info\",\"value\":{{\"haves\":{haves},\"wants\":{wants},\"want-refs\":0,\"depth\":{depth},\"shallows\":{shallows},\"deepen-since\":{deepen_since},\"deepen-not\":{deepen_not},\"deepen-relative\":false,\"filter\":{filter_json}}}}}\n"
875 );
876 if let Ok(mut file) = std::fs::OpenOptions::new()
877 .create(true)
878 .append(true)
879 .open(&path)
880 {
881 use std::io::Write as _;
882 let _ = file.write_all(line.as_bytes());
883 }
884}