use std::path::PathBuf; use git_features::threading::OwnShared; /// A way to configure the usage of replacement objects, see `git replace`. pub enum ReplacementObjects { /// Allow replacement objects and configure the ref prefix the standard environment variable `GIT_REPLACE_REF_BASE`, /// or default to the standard `refs/replace/` prefix. UseWithEnvironmentRefPrefixOrDefault { /// If true, default true, a standard environment variable `GIT_NO_REPLACE_OBJECTS` allow_disable_via_environment: bool, }, /// Use replacement objects and configure the prefix yourself. UseWithRefPrefix { /// The ref prefix to use, like `refs/alternative/` - note the trailing slash. prefix: PathBuf, /// If true, default true, a standard environment variable `GIT_NO_REPLACE_OBJECTS` allow_disable_via_environment: bool, }, /// Do not use replacement objects at all. Disable, } impl Default for ReplacementObjects { fn default() -> Self { ReplacementObjects::UseWithEnvironmentRefPrefixOrDefault { allow_disable_via_environment: true, } } } impl ReplacementObjects { fn refs_prefix(self) -> Option { use ReplacementObjects::*; let is_disabled = |allow_env: bool| allow_env && std::env::var_os("GIT_NO_REPLACE_OBJECTS").is_some(); match self { UseWithEnvironmentRefPrefixOrDefault { allow_disable_via_environment, } => { if is_disabled(allow_disable_via_environment) { return None; }; PathBuf::from(std::env::var("GIT_REPLACE_REF_BASE").unwrap_or_else(|_| "refs/replace/".into())).into() } UseWithRefPrefix { prefix, allow_disable_via_environment, } => { if is_disabled(allow_disable_via_environment) { return None; }; prefix.into() } Disable => None, } } } /// The options used in [`Repository::open_opts #[derive(Default)] pub struct Options { object_store_slots: git_odb::store::init::Slots, replacement_objects: ReplacementObjects, } impl Options { /// Set the amount of slots to use for the object database. It's a value that doesn't need changes on the client, typically, /// but should be controlled on the server. pub fn object_store_slots(mut self, slots: git_odb::store::init::Slots) -> Self { self.object_store_slots = slots; self } // TODO: tests /// Configure replacement objects, see the [`ReplacementObjects`] type for details. pub fn replacement_objects(mut self, config: ReplacementObjects) -> Self { self.replacement_objects = config; self } /// Open a repository at `path` with the options set so far. pub fn open(self, path: impl Into) -> Result { crate::ThreadSafeRepository::open_opts(path, self) } } /// The error returned by [`crate::open()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] Config(#[from] crate::config::Error), #[error(transparent)] NotARepository(#[from] crate::path::is::Error), #[error(transparent)] ObjectStoreInitialization(#[from] std::io::Error), } impl crate::ThreadSafeRepository { /// Open a git repository at the given `path`, possibly expanding it to `path/.git` if `path` is a work tree dir. pub fn open(path: impl Into) -> Result { Self::open_opts(path, Options::default()) } /// Open a git repository at the given `path`, possibly expanding it to `path/.git` if `path` is a work tree dir. fn open_opts(path: impl Into, options: Options) -> Result { let path = path.into(); let (path, kind) = match crate::path::is::git(&path) { Ok(kind) => (path, kind), Err(_) => { let git_dir = path.join(".git"); crate::path::is::git(&git_dir).map(|kind| (git_dir, kind))? } }; let (git_dir, worktree_dir) = crate::Path::from_dot_git_dir(path, kind).into_repository_and_work_tree_directories(); crate::ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options) } pub(crate) fn open_from_paths( git_dir: PathBuf, mut worktree_dir: Option, Options { object_store_slots, replacement_objects, }: Options, ) -> Result { let mut config = crate::config::Cache::new(&git_dir)?; match worktree_dir { None if !config.is_bare => { worktree_dir = Some(git_dir.parent().expect("parent is always available").to_owned()); } Some(_) => { config.is_bare = false; } None => {} } let refs = crate::RefStore::at( &git_dir, if worktree_dir.is_none() { git_ref::store::WriteReflog::Disable } else { git_ref::store::WriteReflog::Normal }, config.object_hash, ); let replacements = replacement_objects .refs_prefix() .and_then(|prefix| { let platform = refs.iter().ok()?; let iter = platform.prefixed(&prefix).ok()?; let prefix = prefix.to_str()?; let replacements = iter .filter_map(Result::ok) .filter_map(|r: git_ref::Reference| { let target = r.target.try_id()?.to_owned(); let source = git_hash::ObjectId::from_hex(r.name.as_bstr().strip_prefix(prefix.as_bytes())?).ok()?; Some((source, target)) }) .collect::>(); Some(replacements) }) .unwrap_or_default(); Ok(crate::ThreadSafeRepository { objects: OwnShared::new(git_odb::Store::at_opts( git_dir.join("objects"), replacements, git_odb::store::init::Options { slots: object_store_slots, object_hash: config.object_hash, use_multi_pack_index: config.use_multi_pack_index, }, )?), refs, work_tree: worktree_dir, config, }) } }