use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::commit_graph_file::CommitGraphChain;
use crate::commit_graph_write::collect_reachable_commit_oids;
use crate::config::ConfigSet;
use crate::error::{Error, Result};
use crate::fsck::reachability_roots;
use crate::gc::prune_loose_unreachable;
use crate::objects::HashAlgo;
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,
commit_graph: Option<Arc<CommitGraphChain>>,
}
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,
commit_graph: None,
}
}
#[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 commit_graph(mut self, chain: Arc<CommitGraphChain>) -> Self {
self.commit_graph = Some(chain);
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
}
pub(crate) fn set_objects_dir(&mut self, objects_dir: PathBuf) {
self.objects_dir = objects_dir;
}
pub(crate) fn has_custom_primary(&self) -> bool {
self.custom_primary.is_some()
}
pub(crate) fn injected_hash_algos(&self) -> impl Iterator<Item = HashAlgo> + '_ {
self.custom_primary
.iter()
.map(|store| store.hash_algo())
.chain(self.read_sources.iter().map(|store| store.hash_algo()))
.chain(self.commit_graph.iter().map(|chain| chain.hash_algo()))
}
#[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.attached_commit_graph = builder.commit_graph;
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 skip = crate::maintenance::CommitGraphSkipReason::detect(git_dir, || {
let enabled = config
.get_bool("core.useReplaceRefs")
.and_then(|r| r.ok())
.unwrap_or(true);
Ok(enabled && !crate::refs::list_refs(git_dir, "refs/replace/")?.is_empty())
})?;
if skip.is_some() {
crate::commit_graph_write::remove_commit_graph_files(objects_dir)?;
return Ok(());
}
let commits = collect_reachable_commit_oids(git_dir, self)?;
let existing = CommitGraphChain::try_load(objects_dir).ok().flatten();
let built = crate::maintenance::build_commit_graph_for_commits(
self,
commits,
&config,
true,
existing.as_ref(),
)?;
crate::commit_graph_write::atomic_replace_commit_graph(objects_dir, &built.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
}
}