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(
77 request.format,
78 &references,
79 &refspecs,
80 request.options.auto_follow_tags,
81 )?;
82 if request.options.fetch_all_tags {
83 mark_tag_refspec_updates_not_for_merge(&mut fetched);
84 order_bundle_fetch_all_tags_updates(&mut fetched);
85 }
86 let store = FileRefStore::new(request.git_dir, request.format);
87 if !request.options.fetch_all_tags {
88 retain_missing_auto_follow_tags(&store, &mut fetched)?;
89 }
90 if request.options.dry_run {
91 return Ok(());
92 }
93 if request.options.write_fetch_head {
94 write_fetch_head(
95 request.git_dir,
96 request.bundle_path,
97 &fetched,
98 request.options.append,
99 )?;
100 }
101 let updates = fetched
102 .iter()
103 .filter_map(|fetched| {
104 fetched.dst.as_ref().map(|dst| BundleRefUpdate {
105 name: dst.clone(),
106 oid: fetched.oid,
107 })
108 })
109 .collect::<Vec<_>>();
110 store.apply_bundle_ref_updates(&updates, None)?;
111 Ok(())
112}
113
114fn bundle_default_fetch_reference(references: &[BundleReference]) -> Result<&BundleReference> {
115 references
116 .iter()
117 .find(|reference| reference.name == "HEAD")
118 .ok_or_else(|| GitError::reference_not_found("remote ref HEAD"))
119}
120
121fn write_bundle_default_fetch_head(
122 git_dir: &Path,
123 bundle_path: &str,
124 reference: &BundleReference,
125 append: bool,
126) -> Result<()> {
127 let records = [FetchHeadRecord {
128 oid: reference.oid,
129 not_for_merge: false,
130 description: bundle_path.to_string(),
131 }];
132 write_fetch_head_records(git_dir, &records, append)?;
133 Ok(())
134}
135
136fn bundle_fetch_refs(
137 format: ObjectFormat,
138 references: &[BundleReference],
139 refspecs: &[String],
140 auto_follow_tags: bool,
141) -> Result<Vec<FetchRefUpdate>> {
142 let refs = references
143 .iter()
144 .map(|reference| RefAdvertisement {
145 oid: reference.oid,
146 name: reference.name.clone(),
147 capabilities: Vec::new(),
148 })
149 .collect::<Vec<_>>();
150 let refspecs = refspecs
151 .iter()
152 .map(|refspec| parse_refspec(refspec))
153 .collect::<Result<Vec<_>>>()?;
154 plan_fetch_ref_updates(format, &refs, &refspecs, auto_follow_tags)
155}