1use std::path::Path;
11
12use sley_core::{GitError, ObjectFormat, Result};
13use sley_formats::{Bundle, BundleReference};
14use sley_odb::{FileObjectDatabase, install_bundle_pack, verify_bundle_prerequisites};
15use sley_protocol::{
16 FetchHeadRecord, FetchRefUpdate, RefAdvertisement, parse_refspec, plan_fetch_ref_updates,
17};
18use sley_refs::{BundleRefUpdate, FileRefStore};
19
20use crate::fetch::{
21 FetchOptions, fetch_refspecs_for_source, mark_tag_refspec_updates_not_for_merge,
22 order_bundle_fetch_all_tags_updates, retain_missing_auto_follow_tags, write_fetch_head,
23 write_fetch_head_records,
24};
25
26pub struct FetchBundleRequest<'a> {
28 pub git_dir: &'a Path,
30 pub format: ObjectFormat,
32 pub bundle_path: &'a str,
34 pub bundle: &'a Bundle,
36 pub refspecs: &'a [String],
39 pub options: &'a FetchOptions,
41}
42
43pub fn fetch_bundle(request: FetchBundleRequest<'_>) -> Result<()> {
51 let prerequisite_reader = FileObjectDatabase::from_git_dir(request.git_dir, request.format);
52 let references = if request.options.dry_run {
53 verify_bundle_prerequisites(request.bundle, &prerequisite_reader)?;
54 request.bundle.references.clone()
55 } else {
56 let database = FileObjectDatabase::from_git_dir(request.git_dir, request.format);
57 install_bundle_pack(request.bundle, &prerequisite_reader, &database)?.references
58 };
59 if request.refspecs.is_empty() {
60 if request.options.dry_run {
61 return Ok(());
62 }
63 if request.options.write_fetch_head {
64 let reference = bundle_default_fetch_reference(&references)?;
65 write_bundle_default_fetch_head(
66 request.git_dir,
67 request.bundle_path,
68 reference,
69 request.options.append,
70 )?;
71 }
72 return Ok(());
73 }
74 let refspecs =
75 fetch_refspecs_for_source(Vec::new(), request.refspecs, request.options.fetch_all_tags);
76 let mut fetched = bundle_fetch_refs(&references, &refspecs, request.options.auto_follow_tags)?;
77 if request.options.fetch_all_tags {
78 mark_tag_refspec_updates_not_for_merge(&mut fetched);
79 order_bundle_fetch_all_tags_updates(&mut fetched);
80 }
81 let store = FileRefStore::new(request.git_dir, request.format);
82 if !request.options.fetch_all_tags {
83 retain_missing_auto_follow_tags(&store, &mut fetched)?;
84 }
85 if request.options.dry_run {
86 return Ok(());
87 }
88 if request.options.write_fetch_head {
89 write_fetch_head(
90 request.git_dir,
91 request.bundle_path,
92 &fetched,
93 request.options.append,
94 )?;
95 }
96 let updates = fetched
97 .iter()
98 .filter_map(|fetched| {
99 fetched.dst.as_ref().map(|dst| BundleRefUpdate {
100 name: dst.clone(),
101 oid: fetched.oid,
102 })
103 })
104 .collect::<Vec<_>>();
105 store.apply_bundle_ref_updates(&updates, None)?;
106 Ok(())
107}
108
109fn bundle_default_fetch_reference(references: &[BundleReference]) -> Result<&BundleReference> {
110 references
111 .iter()
112 .find(|reference| reference.name == "HEAD")
113 .ok_or_else(|| GitError::reference_not_found("remote ref HEAD"))
114}
115
116fn write_bundle_default_fetch_head(
117 git_dir: &Path,
118 bundle_path: &str,
119 reference: &BundleReference,
120 append: bool,
121) -> Result<()> {
122 let records = [FetchHeadRecord {
123 oid: reference.oid,
124 not_for_merge: false,
125 description: bundle_path.to_string(),
126 }];
127 write_fetch_head_records(git_dir, &records, append)?;
128 Ok(())
129}
130
131fn bundle_fetch_refs(
132 references: &[BundleReference],
133 refspecs: &[String],
134 auto_follow_tags: bool,
135) -> Result<Vec<FetchRefUpdate>> {
136 let refs = references
137 .iter()
138 .map(|reference| RefAdvertisement {
139 oid: reference.oid,
140 name: reference.name.clone(),
141 capabilities: Vec::new(),
142 })
143 .collect::<Vec<_>>();
144 let refspecs = refspecs
145 .iter()
146 .map(|refspec| parse_refspec(refspec))
147 .collect::<Result<Vec<_>>>()?;
148 plan_fetch_ref_updates(&refs, &refspecs, auto_follow_tags)
149}