1use std::path::{Path, PathBuf};
22use std::sync::Arc;
23
24use crate::commit_graph_file::CommitGraphChain;
25use crate::commit_graph_write::{
26 build_commit_graph_bytes, collect_reachable_commit_oids, load_commit_graph_commit_info,
27};
28use crate::config::ConfigSet;
29use crate::error::{Error, Result};
30use crate::fsck::reachability_roots;
31use crate::gc::prune_loose_unreachable;
32use crate::objects::{oid_hash_map, ObjectId};
33use crate::odb::store::{ObjectStore, WritableObjectStore};
34use crate::repo::Repository;
35
36use super::Odb;
37
38#[derive(Debug, Clone)]
40pub struct OdbBuilder {
41 objects_dir: PathBuf,
42 custom_primary: Option<Arc<dyn WritableObjectStore>>,
43 read_sources: Vec<Arc<dyn ObjectStore>>,
44 alternates: bool,
45}
46
47impl OdbBuilder {
48 #[must_use]
56 pub fn files(objects_dir: impl AsRef<Path>) -> Self {
57 Self {
58 objects_dir: objects_dir.as_ref().to_path_buf(),
59 custom_primary: None,
60 read_sources: Vec::new(),
61 alternates: true,
62 }
63 }
64
65 #[must_use]
86 pub fn primary(mut self, store: Arc<dyn WritableObjectStore>) -> Self {
87 self.custom_primary = Some(store);
88 self
89 }
90
91 #[must_use]
93 pub fn push_read_source(mut self, store: Arc<dyn ObjectStore>) -> Self {
94 self.read_sources.push(store);
95 self
96 }
97
98 #[must_use]
100 pub fn alternates(mut self, enabled: bool) -> Self {
101 self.alternates = enabled;
102 self
103 }
104
105 #[must_use]
107 pub fn objects_dir(&self) -> &Path {
108 &self.objects_dir
109 }
110
111 pub(crate) fn set_objects_dir(&mut self, objects_dir: PathBuf) {
112 self.objects_dir = objects_dir;
113 }
114
115 pub(crate) fn has_custom_primary(&self) -> bool {
117 self.custom_primary.is_some()
118 }
119
120 #[must_use]
122 pub fn build(self) -> Odb {
123 Odb::from_builder(self)
124 }
125}
126
127impl Odb {
128 #[must_use]
130 pub(crate) fn from_builder(builder: OdbBuilder) -> Self {
131 let mut odb = Self::new(&builder.objects_dir);
132 odb.custom_primary = builder.custom_primary;
133 odb.extra_read_sources = builder.read_sources;
134 odb.alternates_enabled = builder.alternates;
135 odb
136 }
137
138 #[must_use]
140 pub fn uses_files_primary(&self) -> bool {
141 self.custom_primary.is_none()
142 }
143
144 #[must_use]
146 pub fn files_objects_dir(&self) -> Option<&Path> {
147 if self.uses_files_primary() {
148 Some(&self.objects_dir)
149 } else {
150 None
151 }
152 }
153
154 pub(crate) fn require_files_primary(&self, operation: &'static str) -> Result<()> {
156 if self.custom_primary.is_some() {
157 return Err(Error::UnsupportedObjectStore { operation });
158 }
159 Ok(())
160 }
161
162 pub fn primary_writable(&self) -> Result<Arc<dyn WritableObjectStore>> {
168 if let Some(primary) = &self.custom_primary {
169 return Ok(Arc::clone(primary));
170 }
171 Ok(self.primary()? as Arc<dyn WritableObjectStore>)
172 }
173
174 pub fn gc(&self) -> Result<()> {
182 self.require_files_primary("gc")?;
183 let Some(git_dir) = self.config_git_dir() else {
184 return Err(Error::UnsupportedObjectStore { operation: "gc" });
185 };
186 let opts = crate::environment::RepositoryOptions::empty();
187 let repo = if git_dir.file_name().is_some_and(|n| n == ".git") {
188 let wt = git_dir
189 .parent()
190 .ok_or(Error::UnsupportedObjectStore { operation: "gc" })?;
191 Repository::open(git_dir, &opts.with_work_tree(wt))?
192 } else {
193 Repository::open(git_dir, &opts)?
194 };
195 let roots = reachability_roots(&repo)?;
196 prune_loose_unreachable(self, &roots, None)?;
197 Ok(())
198 }
199
200 pub fn write_commit_graph(&self) -> Result<()> {
211 self.require_files_primary("commit-graph write")?;
212 let Some(git_dir) = self.config_git_dir() else {
213 return Err(Error::UnsupportedObjectStore {
214 operation: "commit-graph write",
215 });
216 };
217 let Some(objects_dir) = self.files_objects_dir() else {
218 return Err(Error::UnsupportedObjectStore {
219 operation: "commit-graph write",
220 });
221 };
222 let config = ConfigSet::load(
223 &crate::environment::Environment::empty(),
224 Some(git_dir),
225 true,
226 )
227 .unwrap_or_default();
228 let skip = crate::maintenance::CommitGraphSkipReason::detect(git_dir, || {
229 let enabled = config
230 .get_bool("core.useReplaceRefs")
231 .and_then(|r| r.ok())
232 .unwrap_or(true);
233 Ok(enabled && !crate::refs::list_refs(git_dir, "refs/replace/")?.is_empty())
234 })?;
235 if skip.is_some() {
236 crate::commit_graph_write::remove_commit_graph_files(objects_dir)?;
237 return Ok(());
238 }
239 let bloom = crate::maintenance::bloom_settings_for_commit_graph_write(&config, true);
240 let write_generation = crate::maintenance::commit_graph_writes_generation_data(&config);
241 let commits = collect_reachable_commit_oids(git_dir, self)?;
242 let mut sorted: Vec<ObjectId> = commits.into_iter().collect();
243 sorted.sort();
244 let mut infos = oid_hash_map();
245 for oid in &sorted {
246 infos.insert(*oid, load_commit_graph_commit_info(self, *oid)?);
247 }
248 let chain = CommitGraphChain::try_load(objects_dir).ok().flatten();
249 let mut existing_filters = oid_hash_map();
250 let mut upgraded_filters = oid_hash_map();
251 if let Some(ref chain) = chain {
252 for oid in &sorted {
253 if let Some(bytes) = chain.existing_filter_bytes(oid, &bloom) {
254 existing_filters.insert(*oid, bytes);
255 } else if let Some(bytes) = chain.upgradable_filter_bytes(oid, &bloom) {
256 upgraded_filters.insert(*oid, bytes);
257 }
258 }
259 }
260 use std::collections::HashMap;
261 let infos_map: HashMap<_, _> = infos.iter().map(|(k, v)| (*k, v.clone())).collect();
262 let existing_map: HashMap<_, _> = existing_filters
263 .iter()
264 .map(|(k, v)| (*k, v.clone()))
265 .collect();
266 let upgraded_map: HashMap<_, _> = upgraded_filters
267 .iter()
268 .map(|(k, v)| (*k, v.clone()))
269 .collect();
270 let (bytes, _) = build_commit_graph_bytes(
271 &sorted,
272 &infos_map,
273 self,
274 true,
275 &bloom,
276 None,
277 &[],
278 None,
279 &existing_map,
280 &upgraded_map,
281 write_generation,
282 )?;
283 crate::commit_graph_write::atomic_replace_commit_graph(objects_dir, &bytes)?;
284 let _ = crate::commit_graph_write::remove_commit_graph_chain_dir(objects_dir);
285 Ok(())
286 }
287
288 #[must_use]
290 pub fn with_resolved_work_tree(mut self, work_tree: Option<PathBuf>) -> Self {
291 self.work_tree = work_tree;
292 self
293 }
294}