use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::commit_graph_file::CommitGraphChain;
use crate::commit_graph_write::{
build_commit_graph_bytes, collect_reachable_commit_oids, load_commit_graph_commit_info,
};
use crate::config::ConfigSet;
use crate::error::{Error, Result};
use crate::fsck::reachability_roots;
use crate::gc::prune_loose_unreachable;
use crate::objects::{oid_hash_map, ObjectId};
use crate::odb::store::{ObjectStore, WritableObjectStore};
use crate::repo::Repository;
use super::Odb;
#[derive(Debug, Clone)]
pub struct OdbBuilder {
objects_dir: PathBuf,
custom_primary: Option<Arc<dyn WritableObjectStore>>,
read_sources: Vec<Arc<dyn ObjectStore>>,
alternates: bool,
}
impl OdbBuilder {
#[must_use]
pub fn files(objects_dir: impl AsRef<Path>) -> Self {
Self {
objects_dir: objects_dir.as_ref().to_path_buf(),
custom_primary: None,
read_sources: Vec::new(),
alternates: true,
}
}
#[must_use]
pub fn primary(mut self, store: Arc<dyn WritableObjectStore>) -> Self {
self.custom_primary = Some(store);
self
}
#[must_use]
pub fn push_read_source(mut self, store: Arc<dyn ObjectStore>) -> Self {
self.read_sources.push(store);
self
}
#[must_use]
pub fn alternates(mut self, enabled: bool) -> Self {
self.alternates = enabled;
self
}
#[must_use]
pub fn objects_dir(&self) -> &Path {
&self.objects_dir
}
#[must_use]
pub fn build(self) -> Odb {
Odb::from_builder(self)
}
}
impl Odb {
#[must_use]
pub(crate) fn from_builder(builder: OdbBuilder) -> Self {
let mut odb = Self::new(&builder.objects_dir);
odb.custom_primary = builder.custom_primary;
odb.extra_read_sources = builder.read_sources;
odb.alternates_enabled = builder.alternates;
odb
}
#[must_use]
pub fn uses_files_primary(&self) -> bool {
self.custom_primary.is_none()
}
#[must_use]
pub fn files_objects_dir(&self) -> Option<&Path> {
if self.uses_files_primary() {
Some(&self.objects_dir)
} else {
None
}
}
pub(crate) fn require_files_primary(&self, operation: &'static str) -> Result<()> {
if self.custom_primary.is_some() {
return Err(Error::UnsupportedObjectStore { operation });
}
Ok(())
}
pub fn primary_writable(&self) -> Result<Arc<dyn WritableObjectStore>> {
if let Some(primary) = &self.custom_primary {
return Ok(Arc::clone(primary));
}
Ok(self.primary()? as Arc<dyn WritableObjectStore>)
}
pub fn gc(&self) -> Result<()> {
self.require_files_primary("gc")?;
let Some(git_dir) = self.config_git_dir() else {
return Err(Error::UnsupportedObjectStore { operation: "gc" });
};
let opts = crate::environment::RepositoryOptions::empty();
let repo = if git_dir.file_name().is_some_and(|n| n == ".git") {
let wt = git_dir
.parent()
.ok_or(Error::UnsupportedObjectStore { operation: "gc" })?;
Repository::open(git_dir, &opts.with_work_tree(wt))?
} else {
Repository::open(git_dir, &opts)?
};
let roots = reachability_roots(&repo)?;
prune_loose_unreachable(self, &roots, None)?;
Ok(())
}
pub fn write_commit_graph(&self) -> Result<()> {
self.require_files_primary("commit-graph write")?;
let Some(git_dir) = self.config_git_dir() else {
return Err(Error::UnsupportedObjectStore {
operation: "commit-graph write",
});
};
let Some(objects_dir) = self.files_objects_dir() else {
return Err(Error::UnsupportedObjectStore {
operation: "commit-graph write",
});
};
let config = ConfigSet::load(
&crate::environment::Environment::empty(),
Some(git_dir),
true,
)
.unwrap_or_default();
let bloom = crate::maintenance::bloom_settings_for_commit_graph_write(&config, true);
let write_generation = crate::maintenance::commit_graph_writes_generation_data(&config);
let commits = collect_reachable_commit_oids(git_dir, self)?;
let mut sorted: Vec<ObjectId> = commits.into_iter().collect();
sorted.sort();
let mut infos = oid_hash_map();
for oid in &sorted {
infos.insert(*oid, load_commit_graph_commit_info(self, *oid)?);
}
let chain = CommitGraphChain::try_load(objects_dir).ok().flatten();
let mut existing_filters = oid_hash_map();
let mut upgraded_filters = oid_hash_map();
if let Some(ref chain) = chain {
for oid in &sorted {
if let Some(bytes) = chain.existing_filter_bytes(oid, &bloom) {
existing_filters.insert(*oid, bytes);
} else if let Some(bytes) = chain.upgradable_filter_bytes(oid, &bloom) {
upgraded_filters.insert(*oid, bytes);
}
}
}
use std::collections::HashMap;
let infos_map: HashMap<_, _> = infos.iter().map(|(k, v)| (*k, v.clone())).collect();
let existing_map: HashMap<_, _> = existing_filters
.iter()
.map(|(k, v)| (*k, v.clone()))
.collect();
let upgraded_map: HashMap<_, _> = upgraded_filters
.iter()
.map(|(k, v)| (*k, v.clone()))
.collect();
let (bytes, _) = build_commit_graph_bytes(
&sorted,
&infos_map,
self,
true,
&bloom,
None,
&[],
None,
&existing_map,
&upgraded_map,
write_generation,
)?;
crate::commit_graph_write::atomic_replace_commit_graph(objects_dir, &bytes)?;
let _ = crate::commit_graph_write::remove_commit_graph_chain_dir(objects_dir);
Ok(())
}
#[must_use]
pub fn with_resolved_work_tree(mut self, work_tree: Option<PathBuf>) -> Self {
self.work_tree = work_tree;
self
}
}