/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Repository factory.
#![feature(trait_alias)]

use std::collections::HashMap;
use std::future::Future;
use std::hash::Hash;
use std::num::NonZeroUsize;
use std::sync::Arc;

use acl_regions::build_acl_regions;
use acl_regions::ArcAclRegions;
use anyhow::Context;
use anyhow::Result;
use async_once_cell::AsyncOnceCell;
use blobstore::Blobstore;
use blobstore::BlobstoreEnumerableWithUnlink;
use blobstore_factory::default_scrub_handler;
use blobstore_factory::make_blobstore;
use blobstore_factory::make_blobstore_enumerable_with_unlink;
pub use blobstore_factory::BlobstoreOptions;
use blobstore_factory::ComponentSamplingHandler;
use blobstore_factory::MetadataSqlFactory;
pub use blobstore_factory::ReadOnlyStorage;
use blobstore_factory::ScrubHandler;
use bonsai_blob_mapping::ArcBonsaiBlobMapping;
use bonsai_blob_mapping::BonsaiBlobMapping;
use bonsai_blob_mapping::SqlBonsaiBlobMapping;
use bonsai_git_mapping::ArcBonsaiGitMapping;
use bonsai_git_mapping::SqlBonsaiGitMappingBuilder;
use bonsai_globalrev_mapping::ArcBonsaiGlobalrevMapping;
use bonsai_globalrev_mapping::CachingBonsaiGlobalrevMapping;
use bonsai_globalrev_mapping::SqlBonsaiGlobalrevMappingBuilder;
use bonsai_hg_mapping::ArcBonsaiHgMapping;
use bonsai_hg_mapping::CachingBonsaiHgMapping;
use bonsai_hg_mapping::SqlBonsaiHgMappingBuilder;
use bonsai_svnrev_mapping::ArcBonsaiSvnrevMapping;
use bonsai_svnrev_mapping::CachingBonsaiSvnrevMapping;
use bonsai_svnrev_mapping::SqlBonsaiSvnrevMappingBuilder;
use bonsai_tag_mapping::ArcBonsaiTagMapping;
use bonsai_tag_mapping::SqlBonsaiTagMappingBuilder;
use bookmarks::bookmark_heads_fetcher;
use bookmarks::ArcBookmarkUpdateLog;
use bookmarks::ArcBookmarks;
use bookmarks::CachedBookmarks;
use cacheblob::new_cachelib_blobstore_no_lease;
use cacheblob::new_memcache_blobstore;
use cacheblob::CachelibBlobstoreOptions;
use cacheblob::InProcessLease;
use cacheblob::LeaseOps;
use cacheblob::MemcacheOps;
use caching_commit_graph_storage::CachingCommitGraphStorage;
use caching_ext::CacheHandlerFactory;
use changeset_fetcher::ArcChangesetFetcher;
use changeset_fetcher::SimpleChangesetFetcher;
use changesets::ArcChangesets;
use changesets_impl::CachingChangesets;
use changesets_impl::SqlChangesetsBuilder;
use commit_graph::ArcCommitGraph;
use commit_graph::CommitGraph;
use commit_graph_compat::ChangesetsCommitGraphCompat;
use commit_graph_types::storage::CommitGraphStorage;
use context::CoreContext;
use context::SessionContainer;
use cross_repo_sync::create_commit_syncer_lease;
use dbbookmarks::ArcSqlBookmarks;
use dbbookmarks::SqlBookmarksBuilder;
use deletion_log::ArcDeletionLog;
use deletion_log::DeletionLog;
use deletion_log::SqlDeletionLog;
#[cfg(fbcode_build)]
use derived_data_client_library::Client as DerivationServiceClient;
use derived_data_remote::Address;
use derived_data_remote::DerivationClient;
use derived_data_remote::RemoteDerivationOptions;
use environment::Caching;
use environment::MononokeEnvironment;
use environment::WarmBookmarksCacheDerivedData;
use ephemeral_blobstore::ArcRepoEphemeralStore;
use ephemeral_blobstore::RepoEphemeralStore;
use ephemeral_blobstore::RepoEphemeralStoreBuilder;
use fbinit::FacebookInit;
use filenodes::ArcFilenodes;
use filestore::ArcFilestoreConfig;
use filestore::FilestoreConfig;
use futures_watchdog::WatchdogExt;
use hooks::hook_loader::load_hooks;
use hooks::ArcHookManager;
use hooks::HookManager;
use hooks_content_stores::RepoFileContentManager;
use hooks_content_stores::TextOnlyFileContentManager;
use live_commit_sync_config::CfgrLiveCommitSyncConfig;
use memcache::KeyGen;
use memcache::MemcacheClient;
use mercurial_mutation::ArcHgMutationStore;
use mercurial_mutation::CachedHgMutationStore;
use mercurial_mutation::SqlHgMutationStoreBuilder;
use metaconfig_types::ArcCommonConfig;
use metaconfig_types::ArcRepoConfig;
use metaconfig_types::BlobConfig;
use metaconfig_types::CommonConfig;
use metaconfig_types::MetadataDatabaseConfig;
use metaconfig_types::Redaction;
use metaconfig_types::RepoConfig;
use metaconfig_types::RepoReadOnly;
use mutable_counters::ArcMutableCounters;
use mutable_counters::SqlMutableCountersBuilder;
use mutable_renames::ArcMutableRenames;
use mutable_renames::MutableRenames;
use mutable_renames::SqlMutableRenamesStore;
use newfilenodes::NewFilenodesBuilder;
use parking_lot::Mutex;
use permission_checker::AclProvider;
use phases::ArcPhases;
use preloaded_commit_graph_storage::PreloadedCommitGraphStorage;
use pushrebase_mutation_mapping::ArcPushrebaseMutationMapping;
use pushrebase_mutation_mapping::SqlPushrebaseMutationMappingConnection;
use readonlyblob::ReadOnlyBlobstore;
use redactedblobstore::ArcRedactionConfigBlobstore;
use redactedblobstore::RedactedBlobs;
use redactedblobstore::RedactionConfigBlobstore;
use redactedblobstore::SqlRedactedContentStore;
use rendezvous::RendezVousOptions;
use repo_blobstore::ArcRepoBlobstore;
use repo_blobstore::RepoBlobstore;
use repo_bookmark_attrs::ArcRepoBookmarkAttrs;
use repo_bookmark_attrs::RepoBookmarkAttrs;
use repo_cross_repo::ArcRepoCrossRepo;
use repo_cross_repo::RepoCrossRepo;
use repo_derived_data::ArcRepoDerivedData;
use repo_derived_data::RepoDerivedData;
use repo_derived_data_service::ArcDerivedDataManagerSet;
use repo_derived_data_service::DerivedDataManagerSet;
use repo_identity::ArcRepoIdentity;
use repo_identity::RepoIdentity;
use repo_lock::AlwaysLockedRepoLock;
use repo_lock::ArcRepoLock;
use repo_lock::MutableRepoLock;
use repo_lock::SqlRepoLock;
use repo_permission_checker::ArcRepoPermissionChecker;
use repo_permission_checker::ProdRepoPermissionChecker;
use repo_sparse_profiles::ArcRepoSparseProfiles;
use repo_sparse_profiles::RepoSparseProfiles;
use repo_sparse_profiles::SqlSparseProfilesSizes;
use requests_table::ArcLongRunningRequestsQueue;
use requests_table::SqlLongRunningRequestsQueue;
use scuba_ext::MononokeScubaSampleBuilder;
use segmented_changelog::new_server_segmented_changelog;
use segmented_changelog::new_server_segmented_changelog_manager;
use segmented_changelog::ArcSegmentedChangelogManager;
use segmented_changelog::SegmentedChangelogSqlConnections;
use segmented_changelog_types::ArcSegmentedChangelog;
use slog::o;
use sql_commit_graph_storage::SqlCommitGraphStorageBuilder;
use sql_construct::SqlConstructFromDatabaseConfig;
use sql_construct::SqlConstructFromMetadataDatabaseConfig;
use sql_query_config::ArcSqlQueryConfig;
use sql_query_config::SqlQueryConfig;
use sqlphases::SqlPhasesBuilder;
use streaming_clone::ArcStreamingClone;
use streaming_clone::StreamingCloneBuilder;
use synced_commit_mapping::ArcSyncedCommitMapping;
use synced_commit_mapping::SqlSyncedCommitMapping;
use thiserror::Error;
use tunables::tunables;
use virtually_sharded_blobstore::VirtuallyShardedBlobstore;
use warm_bookmarks_cache::ArcBookmarksCache;
use warm_bookmarks_cache::NoopBookmarksCache;
use warm_bookmarks_cache::WarmBookmarksCacheBuilder;
use wireproto_handler::ArcPushRedirectorMode;
use wireproto_handler::ArcRepoHandlerBase;
use wireproto_handler::ArcTargetRepoDbs;
use wireproto_handler::PushRedirectorBase;
use wireproto_handler::PushRedirectorMode;
use wireproto_handler::PushRedirectorMode::Enabled;
use wireproto_handler::RepoHandlerBase;
use wireproto_handler::TargetRepoDbs;

const DERIVED_DATA_LEASE: &str = "derived-data-lease";

#[derive(Clone)]
struct RepoFactoryCache<K: Clone + Eq + Hash, V: Clone> {
    cache: Arc<Mutex<HashMap<K, Arc<AsyncOnceCell<V>>>>>,
}

impl<K: Clone + Eq + Hash, V: Clone> RepoFactoryCache<K, V> {
    fn new() -> Self {
        RepoFactoryCache {
            cache: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    async fn get_or_try_init<F, Fut>(&self, key: &K, init: F) -> Result<V>
    where
        F: FnOnce() -> Fut,
        Fut: Future<Output = Result<V>>,
    {
        let cell = {
            let mut cache = self.cache.lock();
            match cache.get(key) {
                Some(cell) => {
                    if let Some(value) = cell.get() {
                        return Ok(value.clone());
                    }
                    cell.clone()
                }
                None => {
                    let cell = Arc::new(AsyncOnceCell::new());
                    cache.insert(key.clone(), cell.clone());
                    cell
                }
            }
        };
        let value = cell.get_or_try_init(init).await?;
        Ok(value.clone())
    }
}

pub trait RepoFactoryOverride<T> = Fn(T) -> T + Send + Sync + 'static;

#[derive(Clone)]
pub struct RepoFactory {
    pub env: Arc<MononokeEnvironment>,
    sql_factories: RepoFactoryCache<MetadataDatabaseConfig, Arc<MetadataSqlFactory>>,
    blobstores: RepoFactoryCache<BlobConfig, Arc<dyn Blobstore>>,
    redacted_blobs: RepoFactoryCache<MetadataDatabaseConfig, Arc<RedactedBlobs>>,
    blobstore_override: Option<Arc<dyn RepoFactoryOverride<Arc<dyn Blobstore>>>>,
    scrub_handler: Arc<dyn ScrubHandler>,
    blobstore_component_sampler: Option<Arc<dyn ComponentSamplingHandler>>,
    bonsai_hg_mapping_overwrite: bool,
}

impl RepoFactory {
    pub fn new(env: Arc<MononokeEnvironment>) -> RepoFactory {
        RepoFactory {
            sql_factories: RepoFactoryCache::new(),
            blobstores: RepoFactoryCache::new(),
            redacted_blobs: RepoFactoryCache::new(),
            blobstore_override: None,
            scrub_handler: default_scrub_handler(),
            blobstore_component_sampler: None,
            bonsai_hg_mapping_overwrite: false,
            env,
        }
    }

    pub fn with_blobstore_override(
        &mut self,
        blobstore_override: impl RepoFactoryOverride<Arc<dyn Blobstore>>,
    ) -> &mut Self {
        self.blobstore_override = Some(Arc::new(blobstore_override));
        self
    }

    pub fn with_scrub_handler(&mut self, scrub_handler: Arc<dyn ScrubHandler>) -> &mut Self {
        self.scrub_handler = scrub_handler;
        self
    }

    pub fn with_blobstore_component_sampler(
        &mut self,
        handler: Arc<dyn ComponentSamplingHandler>,
    ) -> &mut Self {
        self.blobstore_component_sampler = Some(handler);
        self
    }

    pub fn with_bonsai_hg_mapping_override(&mut self) -> &mut Self {
        self.bonsai_hg_mapping_overwrite = true;
        self
    }

    pub async fn sql_factory(
        &self,
        config: &MetadataDatabaseConfig,
    ) -> Result<Arc<MetadataSqlFactory>> {
        self.sql_factories
            .get_or_try_init(config, || async move {
                let sql_factory = MetadataSqlFactory::new(
                    self.env.fb,
                    config.clone(),
                    self.env.mysql_options.clone(),
                    self.env.readonly_storage,
                )
                .watched(&self.env.logger)
                .await?;
                Ok(Arc::new(sql_factory))
            })
            .await
    }

    async fn open_sql<T: SqlConstructFromMetadataDatabaseConfig>(
        &self,
        config: &RepoConfig,
    ) -> Result<T> {
        let sql_factory = self.sql_factory(&config.storage_config.metadata).await?;
        sql_factory.open::<T>().await
    }

    async fn blobstore_no_cache(&self, config: &BlobConfig) -> Result<Arc<dyn Blobstore>> {
        make_blobstore(
            self.env.fb,
            config.clone(),
            &self.env.mysql_options,
            self.env.readonly_storage,
            &self.env.blobstore_options,
            &self.env.logger,
            &self.env.config_store,
            &self.scrub_handler,
            self.blobstore_component_sampler.as_ref(),
        )
        .watched(&self.env.logger)
        .await
    }

    async fn repo_blobstore_from_blobstore(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        blobstore: &Arc<dyn Blobstore>,
        common_config: &ArcCommonConfig,
    ) -> Result<RepoBlobstore> {
        let mut blobstore = blobstore.clone();
        if self.env.readonly_storage.0 {
            blobstore = Arc::new(ReadOnlyBlobstore::new(blobstore));
        }

        let redacted_blobs = match repo_config.redaction {
            Redaction::Enabled => {
                let redacted_blobs = self
                    .redacted_blobs(
                        self.ctx(None),
                        &repo_config.storage_config.metadata,
                        common_config,
                    )
                    .await?;
                Some(redacted_blobs)
            }
            Redaction::Disabled => None,
        };

        let censored_scuba_builder = self.censored_scuba_builder(common_config)?;

        let repo_blobstore = RepoBlobstore::new(
            blobstore,
            redacted_blobs,
            repo_identity.id(),
            censored_scuba_builder,
        );

        Ok(repo_blobstore)
    }

    async fn blobstore_enumerable_with_unlink(
        &self,
        config: &BlobConfig,
    ) -> Result<Arc<dyn BlobstoreEnumerableWithUnlink>> {
        make_blobstore_enumerable_with_unlink(
            self.env.fb,
            config.clone(),
            &self.env.blobstore_options,
            &self.env.logger,
        )
        .watched(&self.env.logger)
        .await
    }

    async fn blobstore(&self, config: &BlobConfig) -> Result<Arc<dyn Blobstore>> {
        self.blobstores
            .get_or_try_init(config, || async move {
                let mut blobstore = self.blobstore_no_cache(config).await?;

                match self.env.caching {
                    Caching::Enabled(local_cache_config) => {
                        let fb = self.env.fb;
                        let memcache_blobstore = tokio::task::spawn_blocking(move || {
                            new_memcache_blobstore(fb, blobstore, "multiplexed", "")
                        })
                        .await??;
                        blobstore = cachelib_blobstore(
                            memcache_blobstore,
                            local_cache_config.blobstore_cache_shards,
                            &self.env.blobstore_options.cachelib_options,
                        )?
                    }
                    Caching::LocalOnly(local_cache_config) => {
                        blobstore = cachelib_blobstore(
                            blobstore,
                            local_cache_config.blobstore_cache_shards,
                            &self.env.blobstore_options.cachelib_options,
                        )?;
                    }
                    Caching::Disabled => {}
                };

                if let Some(blobstore_override) = &self.blobstore_override {
                    blobstore = blobstore_override(blobstore);
                }

                Ok(blobstore)
            })
            .await
    }

    pub async fn redacted_blobs(
        &self,
        ctx: CoreContext,
        db_config: &MetadataDatabaseConfig,
        common_config: &ArcCommonConfig,
    ) -> Result<Arc<RedactedBlobs>> {
        self.redacted_blobs
            .get_or_try_init(db_config, || async move {
                let redacted_blobs = if tunables().redaction_config_from_xdb().unwrap_or_default() {
                    let sql_factory = self.sql_factory(db_config).await?;
                    let redacted_content_store =
                        sql_factory.open::<SqlRedactedContentStore>().await?;
                    // Fetch redacted blobs in a separate task so that slow polls
                    // in repo construction don't interfere with the SQL query.
                    tokio::task::spawn(async move {
                        redacted_content_store.get_all_redacted_blobs().await
                    })
                    .await??
                } else {
                    let blobstore = self.redaction_config_blobstore(common_config).await?;
                    RedactedBlobs::from_configerator(
                        &self.env.config_store,
                        &common_config.redaction_config.redaction_sets_location,
                        ctx,
                        blobstore,
                    )
                    .await?
                };
                Ok(Arc::new(redacted_blobs))
            })
            .await
    }

    pub async fn redaction_config_blobstore_from_config(
        &self,
        config: &BlobConfig,
    ) -> Result<ArcRedactionConfigBlobstore> {
        let blobstore = self.blobstore(config).await?;
        Ok(Arc::new(RedactionConfigBlobstore::new(blobstore)))
    }

    fn ctx(&self, repo_identity: Option<&ArcRepoIdentity>) -> CoreContext {
        let logger = repo_identity.map_or_else(
            || self.env.logger.new(o!()),
            |id| {
                let repo_name = String::from(id.name());
                self.env.logger.new(o!("repo" => repo_name))
            },
        );
        let session = SessionContainer::new_with_defaults(self.env.fb);
        session.new_context(logger, self.env.scuba_sample_builder.clone())
    }

    /// Returns a cache builder for the named pool if caching is enabled
    fn cache_handler_factory(&self, name: &str) -> Result<Option<CacheHandlerFactory>> {
        match self.env.caching {
            Caching::Enabled(_) => Ok(Some(CacheHandlerFactory::Shared {
                cachelib_pool: volatile_pool(name)?,
                memcache_client: MemcacheClient::new(self.env.fb)
                    .context("Failed to initialize memcache client")?,
            })),
            Caching::LocalOnly(_) => Ok(Some(CacheHandlerFactory::Local {
                cachelib_pool: volatile_pool(name)?,
            })),
            Caching::Disabled => Ok(None),
        }
    }

    fn censored_scuba_builder(
        &self,
        config: &ArcCommonConfig,
    ) -> Result<MononokeScubaSampleBuilder> {
        let censored_scuba_params = &config.censored_scuba_params;
        let mut builder = MononokeScubaSampleBuilder::with_opt_table(
            self.env.fb,
            censored_scuba_params.table.clone(),
        )?;
        builder.add_common_server_data();
        if let Some(scuba_log_file) = &censored_scuba_params.local_path {
            builder = builder.with_log_file(scuba_log_file)?;
        }
        Ok(builder)
    }

    pub fn acl_provider(&self) -> &dyn AclProvider {
        self.env.acl_provider.as_ref()
    }
}

fn cache_pool(name: &str) -> Result<cachelib::LruCachePool> {
    Ok(cachelib::get_pool(name)
        .ok_or_else(|| RepoFactoryError::MissingCachePool(name.to_string()))?)
}

fn volatile_pool(name: &str) -> Result<cachelib::VolatileLruCachePool> {
    Ok(cachelib::get_volatile_pool(name)?
        .ok_or_else(|| RepoFactoryError::MissingCachePool(name.to_string()))?)
}

pub fn cachelib_blobstore<B: Blobstore + 'static>(
    blobstore: B,
    cache_shards: usize,
    options: &CachelibBlobstoreOptions,
) -> Result<Arc<dyn Blobstore>> {
    const BLOBSTORE_BLOBS_CACHE_POOL: &str = "blobstore-blobs";
    const BLOBSTORE_PRESENCE_CACHE_POOL: &str = "blobstore-presence";

    let blobstore: Arc<dyn Blobstore> = match NonZeroUsize::new(cache_shards) {
        Some(cache_shards) => {
            let blob_pool = volatile_pool(BLOBSTORE_BLOBS_CACHE_POOL)?;
            let presence_pool = volatile_pool(BLOBSTORE_PRESENCE_CACHE_POOL)?;

            Arc::new(VirtuallyShardedBlobstore::new(
                blobstore,
                blob_pool,
                presence_pool,
                cache_shards,
                options.clone(),
            ))
        }
        None => {
            let blob_pool = cache_pool(BLOBSTORE_BLOBS_CACHE_POOL)?;
            let presence_pool = cache_pool(BLOBSTORE_PRESENCE_CACHE_POOL)?;

            Arc::new(new_cachelib_blobstore_no_lease(
                blobstore,
                Arc::new(blob_pool),
                Arc::new(presence_pool),
                options.clone(),
            ))
        }
    };

    Ok(blobstore)
}

#[derive(Debug, Error)]
pub enum RepoFactoryError {
    #[error("Error opening changesets")]
    Changesets,

    #[error("Error opening bookmarks")]
    Bookmarks,

    #[error("Error opening phases")]
    Phases,

    #[error("Error opening bonsai-hg mapping")]
    BonsaiHgMapping,

    #[error("Error opening bonsai-git mapping")]
    BonsaiGitMapping,

    #[error("Error opening bonsai-globalrev mapping")]
    BonsaiGlobalrevMapping,

    #[error("Error opening bonsai-svnrev mapping")]
    BonsaiSvnrevMapping,

    #[error("Error opening bonsai-tag mapping")]
    BonsaiTagMapping,

    #[error("Error opening pushrebase mutation mapping")]
    PushrebaseMutationMapping,

    #[error("Error opening filenodes")]
    Filenodes,

    #[error("Error opening hg mutation store")]
    HgMutationStore,

    #[error("Error opening segmented changelog")]
    SegmentedChangelog,

    #[error("Error starting segmented changelog manager")]
    SegmentedChangelogManager,

    #[error("Missing cache pool: {0}")]
    MissingCachePool(String),

    #[error("Error opening long-running request queue")]
    LongRunningRequestsQueue,

    #[error("Error opening mutable renames")]
    MutableRenames,

    #[error("Error opening cross repo sync mapping")]
    RepoCrossRepo,

    #[error("Error opening mutable counters")]
    MutableCounters,

    #[error("Error creating hook manager")]
    HookManager,

    #[error("Error creating bookmark attributes")]
    RepoBookmarkAttrs,

    #[error("Error creating streaming clone")]
    StreamingClone,

    #[error("Error creating push redirector base")]
    PushRedirectorBase,

    #[error("Error creating target repo DB")]
    TargetRepoDbs,

    #[error("Error creating repo handler base")]
    RepoHandlerBase,

    #[error("Error openning bonsai blob mapping DB")]
    BonsaiBlobMapping,

    #[error("Error openning deletion log DB")]
    SqlDeletionLog,
}

#[facet::factory(name: String, repo_config_param: RepoConfig, common_config_param: CommonConfig)]
impl RepoFactory {
    pub fn repo_config(&self, repo_config_param: &RepoConfig) -> ArcRepoConfig {
        Arc::new(repo_config_param.clone())
    }

    pub fn common_config(&self, common_config_param: &CommonConfig) -> ArcCommonConfig {
        Arc::new(common_config_param.clone())
    }

    pub fn repo_identity(&self, name: &str, repo_config: &ArcRepoConfig) -> ArcRepoIdentity {
        Arc::new(RepoIdentity::new(repo_config.repoid, name.to_string()))
    }

    pub fn caching(&self) -> Caching {
        self.env.caching
    }

    pub async fn changesets(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        commit_graph: &ArcCommitGraph,
    ) -> Result<ArcChangesets> {
        let builder = self
            .open_sql::<SqlChangesetsBuilder>(repo_config)
            .await
            .context(RepoFactoryError::Changesets)?;
        let changesets = builder.build(self.env.rendezvous_options, repo_identity.id());

        let possibly_cached_changesets: ArcChangesets =
            if let Some(cache_handler_factory) = self.cache_handler_factory("changesets")? {
                Arc::new(CachingChangesets::new(
                    Arc::new(changesets),
                    cache_handler_factory,
                ))
            } else {
                Arc::new(changesets)
            };

        Ok(Arc::new(ChangesetsCommitGraphCompat::new(
            self.env.fb,
            possibly_cached_changesets,
            commit_graph.clone(),
            repo_identity.name().to_string(),
            repo_config.commit_graph_config.scuba_table.as_deref(),
        )?))
    }

    pub fn changeset_fetcher(
        &self,
        repo_identity: &ArcRepoIdentity,
        changesets: &ArcChangesets,
    ) -> ArcChangesetFetcher {
        Arc::new(SimpleChangesetFetcher::new(
            changesets.clone(),
            repo_identity.id(),
        ))
    }

    pub async fn sql_bookmarks(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcSqlBookmarks> {
        let sql_bookmarks = self
            .open_sql::<SqlBookmarksBuilder>(repo_config)
            .await
            .context(RepoFactoryError::Bookmarks)?
            .with_repo_id(repo_identity.id());

        Ok(Arc::new(sql_bookmarks))
    }

    pub fn bookmarks(
        &self,
        sql_bookmarks: &ArcSqlBookmarks,
        repo_identity: &ArcRepoIdentity,
    ) -> ArcBookmarks {
        Arc::new(CachedBookmarks::new(
            sql_bookmarks.clone(),
            repo_identity.id(),
        ))
    }

    pub fn bookmark_update_log(&self, sql_bookmarks: &ArcSqlBookmarks) -> ArcBookmarkUpdateLog {
        sql_bookmarks.clone()
    }

    pub async fn phases(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        bookmarks: &ArcBookmarks,
        changeset_fetcher: &ArcChangesetFetcher,
    ) -> Result<ArcPhases> {
        let mut sql_phases_builder = self
            .open_sql::<SqlPhasesBuilder>(repo_config)
            .await
            .context(RepoFactoryError::Phases)?;
        if let Some(cache_handler_factory) = self.cache_handler_factory("phases")? {
            sql_phases_builder.enable_caching(cache_handler_factory);
        }
        let heads_fetcher = bookmark_heads_fetcher(bookmarks.clone());
        Ok(sql_phases_builder.build(repo_identity.id(), changeset_fetcher.clone(), heads_fetcher))
    }

    pub async fn bonsai_hg_mapping(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcBonsaiHgMapping> {
        let mut builder = self
            .open_sql::<SqlBonsaiHgMappingBuilder>(repo_config)
            .await
            .context(RepoFactoryError::BonsaiHgMapping)?;

        if self.bonsai_hg_mapping_overwrite {
            builder = builder.with_overwrite();
        }

        let bonsai_hg_mapping = builder.build(repo_identity.id(), self.env.rendezvous_options);

        if let Some(cache_handler_factory) = self.cache_handler_factory("bonsai_hg_mapping")? {
            Ok(Arc::new(CachingBonsaiHgMapping::new(
                Arc::new(bonsai_hg_mapping),
                cache_handler_factory,
            )))
        } else {
            Ok(Arc::new(bonsai_hg_mapping))
        }
    }

    pub async fn bonsai_git_mapping(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiGitMapping> {
        let bonsai_git_mapping = self
            .open_sql::<SqlBonsaiGitMappingBuilder>(repo_config)
            .await
            .context(RepoFactoryError::BonsaiGitMapping)?
            .build(repo_identity.id());
        Ok(Arc::new(bonsai_git_mapping))
    }

    pub async fn long_running_requests_queue(
        &self,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcLongRunningRequestsQueue> {
        let long_running_requests_queue = self
            .open_sql::<SqlLongRunningRequestsQueue>(repo_config)
            .await
            .context(RepoFactoryError::LongRunningRequestsQueue)?;
        Ok(Arc::new(long_running_requests_queue))
    }

    pub async fn bonsai_globalrev_mapping(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiGlobalrevMapping> {
        let bonsai_globalrev_mapping = self
            .open_sql::<SqlBonsaiGlobalrevMappingBuilder>(repo_config)
            .await
            .context(RepoFactoryError::BonsaiGlobalrevMapping)?
            .build(repo_identity.id());
        if let Some(cache_handler_factory) =
            self.cache_handler_factory("bonsai_globalrev_mapping")?
        {
            Ok(Arc::new(CachingBonsaiGlobalrevMapping::new(
                Arc::new(bonsai_globalrev_mapping),
                cache_handler_factory,
            )))
        } else {
            Ok(Arc::new(bonsai_globalrev_mapping))
        }
    }

    pub async fn bonsai_svnrev_mapping(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiSvnrevMapping> {
        let bonsai_svnrev_mapping = self
            .open_sql::<SqlBonsaiSvnrevMappingBuilder>(repo_config)
            .await
            .context(RepoFactoryError::BonsaiSvnrevMapping)?
            .build(repo_identity.id());
        if let Some(cache_handler_factory) = self.cache_handler_factory("bonsai_svnrev_mapping")? {
            Ok(Arc::new(CachingBonsaiSvnrevMapping::new(
                Arc::new(bonsai_svnrev_mapping),
                cache_handler_factory,
            )))
        } else {
            Ok(Arc::new(bonsai_svnrev_mapping))
        }
    }

    pub async fn bonsai_tag_mapping(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiTagMapping> {
        let bonsai_tag_mapping = self
            .open_sql::<SqlBonsaiTagMappingBuilder>(repo_config)
            .await
            .context(RepoFactoryError::BonsaiTagMapping)?
            .build(repo_identity.id());
        // Caching is not enabled for now, but can be added later if required.
        Ok(Arc::new(bonsai_tag_mapping))
    }

    pub async fn pushrebase_mutation_mapping(
        &self,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcPushrebaseMutationMapping> {
        let conn = self
            .open_sql::<SqlPushrebaseMutationMappingConnection>(repo_config)
            .await
            .context(RepoFactoryError::PushrebaseMutationMapping)?;
        Ok(Arc::new(conn.with_repo_id(repo_config.repoid)))
    }

    pub async fn permission_checker(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
        common_config: &ArcCommonConfig,
    ) -> Result<ArcRepoPermissionChecker> {
        let repo_name = repo_identity.name();
        let permission_checker = ProdRepoPermissionChecker::new(
            &self.env.logger,
            self.env.acl_provider.as_ref(),
            repo_config.hipster_acl.as_deref(),
            repo_config
                .source_control_service
                .service_write_hipster_acl
                .as_deref(),
            repo_config
                .acl_region_config
                .as_ref()
                .map(|config| {
                    config
                        .allow_rules
                        .iter()
                        .map(|rule| rule.hipster_acl.as_str())
                        .collect()
                })
                .unwrap_or_default(),
            repo_name,
            &common_config.global_allowlist,
        )
        .await?;
        Ok(Arc::new(permission_checker))
    }

    pub async fn filenodes(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcFilenodes> {
        let sql_factory = self
            .sql_factory(&repo_config.storage_config.metadata)
            .await?;
        let mut filenodes_builder = sql_factory
            .open_shardable::<NewFilenodesBuilder>()
            .await
            .context(RepoFactoryError::Filenodes)?;
        if let (Some(filenodes_cache_handler_factory), Some(history_cache_handler_factory)) = (
            self.cache_handler_factory("filenodes")?,
            self.cache_handler_factory("filenodes_history")?,
        ) {
            let filenodes_tier = sql_factory.tier_info_shardable::<NewFilenodesBuilder>()?;
            filenodes_builder.enable_caching(
                filenodes_cache_handler_factory,
                history_cache_handler_factory,
                "newfilenodes",
                &filenodes_tier.tier_name,
            );
        }
        Ok(Arc::new(filenodes_builder.build(repo_identity.id())))
    }

    pub async fn hg_mutation_store(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcHgMutationStore> {
        let hg_mutation_store = self
            .open_sql::<SqlHgMutationStoreBuilder>(repo_config)
            .await
            .context(RepoFactoryError::HgMutationStore)?
            .with_repo_id(repo_identity.id());

        if let Some(cache_handler_factory) = self.cache_handler_factory("hg_mutation_store")? {
            Ok(Arc::new(CachedHgMutationStore::new(
                Arc::new(hg_mutation_store),
                cache_handler_factory,
            )))
        } else {
            Ok(Arc::new(hg_mutation_store))
        }
    }

    pub async fn segmented_changelog(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
        changeset_fetcher: &ArcChangesetFetcher,
        bookmarks: &ArcBookmarks,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcSegmentedChangelog> {
        let sql_connections = self
            .open_sql::<SegmentedChangelogSqlConnections>(repo_config)
            .await
            .context(RepoFactoryError::SegmentedChangelog)?;
        let cache_handler_factory = self.cache_handler_factory("segmented_changelog")?;
        let segmented_changelog = new_server_segmented_changelog(
            &self.ctx(Some(repo_identity)),
            repo_identity,
            repo_config.segmented_changelog_config.clone(),
            sql_connections,
            changeset_fetcher.clone(),
            bookmarks.clone(),
            repo_blobstore.clone(),
            cache_handler_factory,
        )
        .await
        .context(RepoFactoryError::SegmentedChangelog)?;
        Ok(segmented_changelog)
    }

    pub async fn segmented_changelog_manager(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
        changeset_fetcher: &ArcChangesetFetcher,
        bookmarks: &ArcBookmarks,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcSegmentedChangelogManager> {
        let sql_connections = self
            .open_sql::<SegmentedChangelogSqlConnections>(repo_config)
            .await
            .context(RepoFactoryError::SegmentedChangelogManager)?;
        let cache_handler_factory = self.cache_handler_factory("segmented_changelog")?;
        let manager = new_server_segmented_changelog_manager(
            &self.ctx(Some(repo_identity)),
            repo_identity,
            repo_config.segmented_changelog_config.clone(),
            sql_connections,
            changeset_fetcher.clone(),
            bookmarks.clone(),
            repo_blobstore.clone(),
            cache_handler_factory,
        )
        .await
        .context(RepoFactoryError::SegmentedChangelogManager)?;
        Ok(Arc::new(manager))
    }

    pub fn repo_derived_data(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        changesets: &ArcChangesets,
        commit_graph: &ArcCommitGraph,
        bonsai_hg_mapping: &ArcBonsaiHgMapping,
        filenodes: &ArcFilenodes,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcRepoDerivedData> {
        let config = repo_config.derived_data_config.clone();
        let lease = lease_init(self.env.fb, self.env.caching, DERIVED_DATA_LEASE)?;
        let scuba = build_scuba(
            self.env.fb,
            config.scuba_table.clone(),
            repo_identity.name(),
        )?;
        let derivation_service_client =
            get_derivation_client(self.env.fb, self.env.remote_derivation_options.clone())?;
        Ok(Arc::new(RepoDerivedData::new(
            repo_identity.id(),
            repo_identity.name().to_string(),
            changesets.clone(),
            commit_graph.clone(),
            bonsai_hg_mapping.clone(),
            filenodes.clone(),
            repo_blobstore.as_ref().clone(),
            lease,
            scuba,
            config,
            derivation_service_client,
        )?))
    }

    pub async fn repo_blobstore(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        common_config: &ArcCommonConfig,
    ) -> Result<ArcRepoBlobstore> {
        let blobstore = self
            .blobstore(&repo_config.storage_config.blobstore)
            .await?;
        Ok(Arc::new(
            self.repo_blobstore_from_blobstore(
                repo_identity,
                repo_config,
                &blobstore,
                common_config,
            )
            .await?,
        ))
    }

    pub fn filestore_config(&self, repo_config: &ArcRepoConfig) -> ArcFilestoreConfig {
        let filestore_config = repo_config.filestore.as_ref().map_or_else(
            FilestoreConfig::no_chunking_filestore,
            |p| FilestoreConfig {
                chunk_size: Some(p.chunk_size),
                concurrency: p.concurrency,
            },
        );
        Arc::new(filestore_config)
    }

    pub async fn redaction_config_blobstore(
        &self,
        common_config: &ArcCommonConfig,
    ) -> Result<ArcRedactionConfigBlobstore> {
        self.redaction_config_blobstore_from_config(&common_config.redaction_config.blobstore)
            .await
    }

    pub async fn repo_ephemeral_store(
        &self,
        sql_query_config: &ArcSqlQueryConfig,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcRepoEphemeralStore> {
        if let Some(ephemeral_config) = &repo_config.storage_config.ephemeral_blobstore {
            let blobstore = self
                .blobstore_enumerable_with_unlink(&ephemeral_config.blobstore)
                .await?;
            let ephemeral_blobstore = RepoEphemeralStoreBuilder::with_database_config(
                self.env.fb,
                &ephemeral_config.metadata,
                &self.env.mysql_options,
                self.env.readonly_storage.0,
            )?
            .build(
                repo_identity.id(),
                blobstore,
                sql_query_config.clone(),
                ephemeral_config.initial_bubble_lifespan,
                ephemeral_config.bubble_expiration_grace,
                ephemeral_config.bubble_deletion_mode,
            );
            Ok(Arc::new(ephemeral_blobstore))
        } else {
            Ok(Arc::new(RepoEphemeralStore::disabled(repo_identity.id())))
        }
    }

    pub async fn mutable_renames(&self, repo_config: &ArcRepoConfig) -> Result<ArcMutableRenames> {
        let sql_store = self
            .open_sql::<SqlMutableRenamesStore>(repo_config)
            .await
            .context(RepoFactoryError::MutableRenames)?;
        let cache_handler_factory = self.cache_handler_factory("mutable_renames")?;
        Ok(Arc::new(MutableRenames::new(
            repo_config.repoid,
            sql_store,
            cache_handler_factory,
        )?))
    }

    pub fn derived_data_manager_set(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        changesets: &ArcChangesets,
        commit_graph: &ArcCommitGraph,
        bonsai_hg_mapping: &ArcBonsaiHgMapping,
        filenodes: &ArcFilenodes,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcDerivedDataManagerSet> {
        let config = repo_config.derived_data_config.clone();
        let lease = lease_init(self.env.fb, self.env.caching, DERIVED_DATA_LEASE)?;
        let ctx = self.ctx(Some(repo_identity));
        let logger = ctx.logger().clone();
        let derived_data_scuba = build_scuba(
            self.env.fb,
            config.scuba_table.clone(),
            repo_identity.name(),
        )?;
        let derivation_service_client =
            get_derivation_client(self.env.fb, self.env.remote_derivation_options.clone())?;
        anyhow::Ok(Arc::new(DerivedDataManagerSet::new(
            repo_identity.id(),
            repo_identity.name().to_string(),
            changesets.clone(),
            commit_graph.clone(),
            bonsai_hg_mapping.clone(),
            filenodes.clone(),
            repo_blobstore.as_ref().clone(),
            lease,
            logger,
            derived_data_scuba,
            config,
            derivation_service_client,
        )?))
    }

    /// The commit mapping bettween repos for synced commits.
    pub async fn synced_commit_mapping(
        &self,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcSyncedCommitMapping> {
        Ok(Arc::new(
            self.open_sql::<SqlSyncedCommitMapping>(repo_config).await?,
        ))
    }

    /// Cross-repo sync manager for this repo
    pub async fn repo_cross_repo(
        &self,
        repo_identity: &ArcRepoIdentity,
        synced_commit_mapping: &ArcSyncedCommitMapping,
    ) -> Result<ArcRepoCrossRepo> {
        let sync_lease = create_commit_syncer_lease(self.env.fb, self.env.caching)?;
        let logger = self
            .env
            .logger
            .new(o!("repo" => repo_identity.name().to_string()));
        let live_commit_sync_config = Arc::new(CfgrLiveCommitSyncConfig::new(
            &logger,
            &self.env.config_store,
        )?);

        Ok(Arc::new(RepoCrossRepo::new(
            synced_commit_mapping.clone(),
            live_commit_sync_config,
            sync_lease,
        )))
    }

    pub async fn mutable_counters(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcMutableCounters> {
        Ok(Arc::new(
            self.open_sql::<SqlMutableCountersBuilder>(repo_config)
                .await
                .context(RepoFactoryError::MutableCounters)?
                .build(repo_identity.id()),
        ))
    }

    pub fn acl_regions(
        &self,
        repo_config: &ArcRepoConfig,
        commit_graph: &ArcCommitGraph,
    ) -> ArcAclRegions {
        build_acl_regions(repo_config.acl_region_config.as_ref(), commit_graph.clone())
    }

    pub async fn hook_manager(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
        repo_derived_data: &ArcRepoDerivedData,
        bookmarks: &ArcBookmarks,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcHookManager> {
        let name = repo_identity.name();

        let content_store = RepoFileContentManager::from_parts(
            bookmarks.clone(),
            repo_blobstore.clone(),
            repo_derived_data.clone(),
        );

        let disabled_hooks = self
            .env
            .disabled_hooks
            .get(name)
            .cloned()
            .unwrap_or_default();

        let hooks_scuba_local_path = repo_config.scuba_local_path_hooks.clone();

        let mut hooks_scuba = MononokeScubaSampleBuilder::with_opt_table(
            self.env.fb,
            repo_config.scuba_table_hooks.clone(),
        )?;

        hooks_scuba.add("repo", name);

        if let Some(hooks_scuba_local_path) = hooks_scuba_local_path {
            hooks_scuba = hooks_scuba.with_log_file(hooks_scuba_local_path)?;
        }

        let hook_manager = async {
            let fetcher = Box::new(TextOnlyFileContentManager::new(
                content_store,
                repo_config.hook_max_file_size,
            ));

            let mut hook_manager = HookManager::new(
                self.env.fb,
                self.env.acl_provider.as_ref(),
                fetcher,
                repo_config.hook_manager_params.clone().unwrap_or_default(),
                hooks_scuba,
                name.to_string(),
            )
            .await?;

            load_hooks(
                self.env.fb,
                self.env.acl_provider.as_ref(),
                &mut hook_manager,
                repo_config,
                &disabled_hooks,
            )
            .await?;

            <Result<_, anyhow::Error>>::Ok(hook_manager)
        }
        .watched(&self.env.logger)
        .await
        .context(RepoFactoryError::HookManager)?;

        Ok(Arc::new(hook_manager))
    }

    pub async fn sparse_profiles(
        &self,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcRepoSparseProfiles> {
        let sql_profile_sizes = self
            .open_sql::<SqlSparseProfilesSizes>(repo_config)
            .await
            .ok();
        Ok(Arc::new(RepoSparseProfiles { sql_profile_sizes }))
    }

    pub fn repo_lock(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcRepoLock> {
        match repo_config.readonly {
            RepoReadOnly::ReadOnly(ref reason) => Ok(Arc::new(AlwaysLockedRepoLock::new(
                repo_identity.id(),
                reason.clone(),
            ))),
            RepoReadOnly::ReadWrite => {
                let sql = SqlRepoLock::with_metadata_database_config(
                    self.env.fb,
                    &repo_config.storage_config.metadata,
                    &self.env.mysql_options,
                    self.env.readonly_storage.0,
                )?;

                Ok(Arc::new(MutableRepoLock::new(sql, repo_identity.id())))
            }
        }
    }

    pub async fn repo_bookmark_attrs(
        &self,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcRepoBookmarkAttrs> {
        let repo_bookmark_attrs = RepoBookmarkAttrs::new(
            self.env.acl_provider.as_ref(),
            repo_config.bookmarks.clone(),
        )
        .await
        .context(RepoFactoryError::RepoBookmarkAttrs)?;
        Ok(Arc::new(repo_bookmark_attrs))
    }

    pub async fn streaming_clone(
        &self,
        repo_config: &ArcRepoConfig,
        repo_identity: &ArcRepoIdentity,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcStreamingClone> {
        let streaming_clone = self
            .open_sql::<StreamingCloneBuilder>(repo_config)
            .await
            .context(RepoFactoryError::StreamingClone)?
            .build(repo_identity.id(), repo_blobstore.clone());
        Ok(Arc::new(streaming_clone))
    }

    pub async fn warm_bookmarks_cache(
        &self,
        bookmarks: &ArcBookmarks,
        bookmark_update_log: &ArcBookmarkUpdateLog,
        repo_identity: &ArcRepoIdentity,
        repo_derived_data: &ArcRepoDerivedData,
        phases: &ArcPhases,
    ) -> Result<ArcBookmarksCache> {
        match self.env.warm_bookmarks_cache_derived_data {
            Some(derived_data) => {
                let mut scuba = self.env.warm_bookmarks_cache_scuba_sample_builder.clone();
                scuba.add("repo", repo_identity.name());

                let mut wbc_builder = WarmBookmarksCacheBuilder::new(
                    self.ctx(Some(repo_identity)),
                    bookmarks.clone(),
                    bookmark_update_log.clone(),
                    repo_identity.clone(),
                );

                match derived_data {
                    WarmBookmarksCacheDerivedData::HgOnly => {
                        wbc_builder.add_hg_warmers(repo_derived_data, phases)?;
                    }
                    WarmBookmarksCacheDerivedData::AllKinds => {
                        wbc_builder.add_all_warmers(repo_derived_data, phases)?;
                    }
                    WarmBookmarksCacheDerivedData::NoDerivation => {}
                }

                Ok(Arc::new(
                    wbc_builder.build().watched(&self.env.logger).await?,
                ))
            }
            None => Ok(Arc::new(NoopBookmarksCache::new(bookmarks.clone()))),
        }
    }

    pub async fn target_repo_dbs(
        &self,
        bookmarks: &ArcBookmarks,
        bookmark_update_log: &ArcBookmarkUpdateLog,
        mutable_counters: &ArcMutableCounters,
    ) -> Result<ArcTargetRepoDbs> {
        let target_repo_dbs = TargetRepoDbs {
            bookmarks: bookmarks.clone(),
            bookmark_update_log: bookmark_update_log.clone(),
            counters: mutable_counters.clone(),
        };
        Ok(Arc::new(target_repo_dbs))
    }

    pub async fn push_redirector_mode(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_cross_repo: &ArcRepoCrossRepo,
        target_repo_dbs: &ArcTargetRepoDbs,
    ) -> Result<ArcPushRedirectorMode> {
        let common_commit_sync_config = repo_cross_repo
            .live_commit_sync_config()
            .clone()
            .get_common_config_if_exists(repo_identity.id())
            .context(RepoFactoryError::PushRedirectorBase)?;
        let synced_commit_mapping = repo_cross_repo.synced_commit_mapping();

        let push_redirector_mode = match common_commit_sync_config {
            Some(common_commit_sync_config)
                if common_commit_sync_config.large_repo_id != repo_identity.id() =>
            {
                PushRedirectorMode::Enabled(Arc::new(PushRedirectorBase {
                    common_commit_sync_config,
                    synced_commit_mapping: synced_commit_mapping.clone(),
                    target_repo_dbs: target_repo_dbs.clone(),
                }))
            }
            _ => PushRedirectorMode::Disabled,
        };

        Ok(Arc::new(push_redirector_mode))
    }

    pub async fn repo_handler_base(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        push_redirector_mode: &ArcPushRedirectorMode,
    ) -> Result<ArcRepoHandlerBase> {
        let ctx = self.ctx(Some(repo_identity));
        let scuba = ctx.scuba().clone();
        let logger = ctx.logger().clone();
        let repo_client_knobs = repo_config.repo_client_knobs.clone();
        let backup_repo_config = repo_config.backup_repo_config.clone();
        let maybe_push_redirector_base = match **push_redirector_mode {
            Enabled(ref push_redirector_base) => Some(Arc::clone(push_redirector_base)),
            PushRedirectorMode::Disabled => None,
        };
        Ok(Arc::new(RepoHandlerBase {
            logger,
            scuba,
            maybe_push_redirector_base,
            repo_client_knobs,
            backup_repo_config,
        }))
    }

    pub async fn sql_query_config(&self) -> Result<ArcSqlQueryConfig> {
        let caching = if let Some(cache_handler_factory) = self.cache_handler_factory("sql")? {
            const KEY_PREFIX: &str = "scm.mononoke.sql";
            const MC_CODEVER: u32 = 0;
            let sitever: u32 = tunables()
                .sql_memcache_sitever()
                .unwrap_or_default()
                .try_into()
                .unwrap_or(0);
            Some(sql_query_config::CachingConfig {
                keygen: KeyGen::new(KEY_PREFIX, MC_CODEVER, sitever),
                cache_handler_factory,
            })
        } else {
            None
        };
        Ok(Arc::new(SqlQueryConfig { caching }))
    }

    pub async fn commit_graph(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        common_config: &ArcCommonConfig,
    ) -> Result<ArcCommitGraph> {
        let sql_storage = self
            .open_sql::<SqlCommitGraphStorageBuilder>(repo_config)
            .await?
            .build(
                RendezVousOptions {
                    free_connections: 5,
                },
                repo_identity.id(),
            );

        let maybe_cached_storage: Arc<dyn CommitGraphStorage> =
            if let Some(cache_handler_factory) = self.cache_handler_factory("commit_graph")? {
                Arc::new(CachingCommitGraphStorage::new(
                    Arc::new(sql_storage),
                    cache_handler_factory,
                ))
            } else {
                Arc::new(sql_storage)
            };

        match &repo_config
            .commit_graph_config
            .preloaded_commit_graph_blobstore_key
        {
            Some(preloaded_commit_graph_key) => {
                let blobstore_without_cache = self
                    .repo_blobstore_from_blobstore(
                        repo_identity,
                        repo_config,
                        &self
                            .blobstore_no_cache(&repo_config.storage_config.blobstore)
                            .await?,
                        common_config,
                    )
                    .await?;

                let preloaded_commit_graph_storage = PreloadedCommitGraphStorage::from_blobstore(
                    &self.ctx(Some(repo_identity)),
                    repo_identity.id(),
                    Arc::new(blobstore_without_cache),
                    preloaded_commit_graph_key.clone(),
                    maybe_cached_storage,
                )
                .await?;

                Ok(Arc::new(CommitGraph::new(preloaded_commit_graph_storage)))
            }
            None => Ok(Arc::new(CommitGraph::new(maybe_cached_storage))),
        }
    }

    pub async fn bonsai_blob_mapping(
        &self,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcBonsaiBlobMapping> {
        let sql_factory = self
            .sql_factory(&repo_config.storage_config.metadata)
            .await?;
        let sql_bonsai_blob_mapping = sql_factory
            .open_shardable::<SqlBonsaiBlobMapping>()
            .await
            .context(RepoFactoryError::BonsaiBlobMapping)?;
        Ok(Arc::new(BonsaiBlobMapping {
            sql_bonsai_blob_mapping,
        }))
    }

    pub async fn deletion_log(&self, repo_config: &ArcRepoConfig) -> Result<ArcDeletionLog> {
        let sql_deletion_log = self
            .open_sql::<SqlDeletionLog>(repo_config)
            .await
            .context(RepoFactoryError::SqlDeletionLog)?;
        Ok(Arc::new(DeletionLog { sql_deletion_log }))
    }
}

fn lease_init(
    fb: FacebookInit,
    caching: Caching,
    lease_type: &'static str,
) -> Result<Arc<dyn LeaseOps>> {
    // Derived data leasing is performed through the cache, so is only
    // available if caching is enabled.
    if let Caching::Enabled(_) = caching {
        Ok(Arc::new(MemcacheOps::new(fb, lease_type, "")?))
    } else {
        Ok(Arc::new(InProcessLease::new()))
    }
}

fn build_scuba(
    fb: FacebookInit,
    scuba_table: Option<String>,
    reponame: &str,
) -> Result<MononokeScubaSampleBuilder> {
    let mut scuba = MononokeScubaSampleBuilder::with_opt_table(fb, scuba_table)?;
    scuba.add_common_server_data();
    scuba.add("reponame", reponame);
    Ok(scuba)
}

fn get_derivation_client(
    fb: FacebookInit,
    remote_derivation_options: RemoteDerivationOptions,
) -> Result<Option<Arc<dyn DerivationClient>>> {
    let derivation_service_client: Option<Arc<dyn DerivationClient>> =
        if remote_derivation_options.derive_remotely {
            #[cfg(fbcode_build)]
            {
                let client = match remote_derivation_options.address {
                    Address::SmcTier(smc_tier) => {
                        DerivationServiceClient::from_tier_name(fb, smc_tier)?
                    }
                    Address::HostPort(host_port) => {
                        DerivationServiceClient::from_host_port(fb, host_port)?
                    }
                    Address::Empty => DerivationServiceClient::new(fb)?,
                };
                Some(Arc::new(client))
            }
            #[cfg(not(fbcode_build))]
            {
                let _ = fb;
                None
            }
        } else {
            None
        };
    Ok(derivation_service_client)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Repository factory for tests.
#![deny(missing_docs)]

use std::sync::Arc;

use acl_regions::build_acl_regions;
use acl_regions::ArcAclRegions;
use anyhow::Result;
use basename_suffix_skeleton_manifest::RootBasenameSuffixSkeletonManifest;
use blame::RootBlameV2;
use blobstore::Blobstore;
use bonsai_git_mapping::ArcBonsaiGitMapping;
use bonsai_git_mapping::SqlBonsaiGitMappingBuilder;
use bonsai_globalrev_mapping::ArcBonsaiGlobalrevMapping;
use bonsai_globalrev_mapping::SqlBonsaiGlobalrevMappingBuilder;
use bonsai_hg_mapping::ArcBonsaiHgMapping;
use bonsai_hg_mapping::SqlBonsaiHgMappingBuilder;
use bonsai_svnrev_mapping::ArcBonsaiSvnrevMapping;
use bonsai_svnrev_mapping::SqlBonsaiSvnrevMappingBuilder;
use bonsai_tag_mapping::ArcBonsaiTagMapping;
use bonsai_tag_mapping::SqlBonsaiTagMappingBuilder;
use bookmarks::bookmark_heads_fetcher;
use bookmarks::ArcBookmarkUpdateLog;
use bookmarks::ArcBookmarks;
use bookmarks::BookmarkKey;
use cacheblob::InProcessLease;
use cacheblob::LeaseOps;
use changeset_fetcher::ArcChangesetFetcher;
use changeset_fetcher::SimpleChangesetFetcher;
use changeset_info::ChangesetInfo;
use changesets::ArcChangesets;
use changesets_impl::SqlChangesetsBuilder;
use commit_graph::ArcCommitGraph;
use commit_graph::CommitGraph;
use commit_graph_compat::ChangesetsCommitGraphCompat;
use context::CoreContext;
use dbbookmarks::ArcSqlBookmarks;
use dbbookmarks::SqlBookmarksBuilder;
use deleted_manifest::RootDeletedManifestV2Id;
use derived_data_manager::BonsaiDerivable;
use ephemeral_blobstore::ArcRepoEphemeralStore;
use ephemeral_blobstore::RepoEphemeralStore;
use fastlog::RootFastlog;
use fbinit::FacebookInit;
use filenodes::ArcFilenodes;
use filenodes_derivation::FilenodesOnlyPublic;
use filestore::ArcFilestoreConfig;
use filestore::FilestoreConfig;
use fsnodes::RootFsnodeId;
use git_types::TreeHandle;
use hooks::ArcHookManager;
use hooks::HookManager;
use hooks_content_stores::RepoFileContentManager;
use live_commit_sync_config::TestLiveCommitSyncConfig;
use maplit::hashmap;
use maplit::hashset;
use megarepo_mapping::MegarepoMapping;
use memblob::Memblob;
use mercurial_derivation::MappedHgChangesetId;
use mercurial_mutation::ArcHgMutationStore;
use mercurial_mutation::SqlHgMutationStoreBuilder;
use metaconfig_types::ArcRepoConfig;
use metaconfig_types::BlameVersion;
use metaconfig_types::DerivedDataConfig;
use metaconfig_types::DerivedDataTypesConfig;
use metaconfig_types::HookManagerParams;
use metaconfig_types::InfinitepushNamespace;
use metaconfig_types::InfinitepushParams;
use metaconfig_types::RepoConfig;
use metaconfig_types::SegmentedChangelogConfig;
use metaconfig_types::SegmentedChangelogHeadConfig;
use metaconfig_types::SourceControlServiceParams;
use metaconfig_types::UnodeVersion;
use mononoke_types::RepositoryId;
use mutable_counters::ArcMutableCounters;
use mutable_counters::SqlMutableCountersBuilder;
use mutable_renames::ArcMutableRenames;
use mutable_renames::MutableRenames;
use mutable_renames::SqlMutableRenamesStore;
use newfilenodes::NewFilenodesBuilder;
use phases::ArcPhases;
use pushrebase_mutation_mapping::ArcPushrebaseMutationMapping;
use pushrebase_mutation_mapping::SqlPushrebaseMutationMappingConnection;
use redactedblobstore::RedactedBlobs;
use regex::Regex;
use rendezvous::RendezVousOptions;
use repo_blobstore::ArcRepoBlobstore;
use repo_blobstore::RepoBlobstore;
use repo_bookmark_attrs::ArcRepoBookmarkAttrs;
use repo_bookmark_attrs::RepoBookmarkAttrs;
use repo_cross_repo::ArcRepoCrossRepo;
use repo_cross_repo::RepoCrossRepo;
use repo_derived_data::ArcRepoDerivedData;
use repo_derived_data::RepoDerivedData;
use repo_derived_data_service::ArcDerivedDataManagerSet;
use repo_derived_data_service::DerivedDataManagerSet;
use repo_identity::ArcRepoIdentity;
use repo_identity::RepoIdentity;
use repo_lock::AlwaysUnlockedRepoLock;
use repo_lock::ArcRepoLock;
use repo_lock::SqlRepoLock;
use repo_permission_checker::AlwaysAllowRepoPermissionChecker;
use repo_permission_checker::ArcRepoPermissionChecker;
use repo_sparse_profiles::ArcRepoSparseProfiles;
use repo_sparse_profiles::RepoSparseProfiles;
use repo_sparse_profiles::SqlSparseProfilesSizes;
use requests_table::SqlLongRunningRequestsQueue;
use scuba_ext::MononokeScubaSampleBuilder;
use segmented_changelog::new_test_segmented_changelog;
use segmented_changelog::SegmentedChangelogSqlConnections;
use segmented_changelog_types::ArcSegmentedChangelog;
use skeleton_manifest::RootSkeletonManifestId;
use sql::rusqlite::Connection as SqliteConnection;
use sql::sqlite::SqliteCallbacks;
use sql::Connection;
use sql::SqlConnections;
use sql_commit_graph_storage::SqlCommitGraphStorageBuilder;
use sql_construct::SqlConstruct;
use sql_query_config::ArcSqlQueryConfig;
use sql_query_config::SqlQueryConfig;
use sqlphases::SqlPhasesBuilder;
use streaming_clone::ArcStreamingClone;
use streaming_clone::StreamingCloneBuilder;
use synced_commit_mapping::ArcSyncedCommitMapping;
use synced_commit_mapping::SqlSyncedCommitMapping;
use unodes::RootUnodeManifestId;
use warm_bookmarks_cache::ArcBookmarksCache;
use warm_bookmarks_cache::WarmBookmarksCacheBuilder;
use wireproto_handler::ArcRepoHandlerBase;
use wireproto_handler::PushRedirectorBase;
use wireproto_handler::RepoHandlerBase;
use wireproto_handler::TargetRepoDbs;

/// Factory to construct test repositories.
///
/// This factory acts as a long-lived builder which can produce multiple
/// repositories with shared back-end storage or based on similar or the same
/// config.
///
/// By default, it will use a single in-memory blobstore and a single
/// in-memory metadata database for all repositories.
pub struct TestRepoFactory {
    /// Sometimes needed to construct a facet
    pub fb: FacebookInit,
    name: String,
    config: RepoConfig,
    blobstore: Arc<dyn Blobstore>,
    metadata_db: SqlConnections,
    hg_mutation_db: SqlConnections,
    redacted: Option<Arc<RedactedBlobs>>,
    permission_checker: Option<ArcRepoPermissionChecker>,
    derived_data_lease: Option<Box<dyn Fn() -> Arc<dyn LeaseOps> + Send + Sync>>,
    filenodes_override: Option<Box<dyn Fn(ArcFilenodes) -> ArcFilenodes + Send + Sync>>,
}

/// The default configuration for test repositories.
///
/// This configuration enables all derived data types at the latest version.
pub fn default_test_repo_config() -> RepoConfig {
    let derived_data_types_config = DerivedDataTypesConfig {
        types: hashset! {
            RootBlameV2::NAME.to_string(),
            FilenodesOnlyPublic::NAME.to_string(),
            ChangesetInfo::NAME.to_string(),
            RootFastlog::NAME.to_string(),
            RootFsnodeId::NAME.to_string(),
            RootDeletedManifestV2Id::NAME.to_string(),
            RootUnodeManifestId::NAME.to_string(),
            TreeHandle::NAME.to_string(),
            MappedHgChangesetId::NAME.to_string(),
            RootSkeletonManifestId::NAME.to_string(),
            RootBasenameSuffixSkeletonManifest::NAME.to_string(),
        },
        unode_version: UnodeVersion::V2,
        blame_version: BlameVersion::V2,
        ..Default::default()
    };
    RepoConfig {
        derived_data_config: DerivedDataConfig {
            scuba_table: None,
            enabled_config_name: "default".to_string(),
            available_configs: hashmap![
                "default".to_string() => derived_data_types_config.clone(),
                "backfilling".to_string() => derived_data_types_config
            ],
        },
        segmented_changelog_config: SegmentedChangelogConfig {
            enabled: true,
            heads_to_include: vec![SegmentedChangelogHeadConfig::Bookmark(
                BookmarkKey::new("master").unwrap(),
            )],
            ..Default::default()
        },
        infinitepush: InfinitepushParams {
            namespace: Some(InfinitepushNamespace::new(
                Regex::new("scratch/.+").unwrap(),
            )),
            ..Default::default()
        },
        source_control_service: SourceControlServiceParams {
            permit_writes: true,
            ..Default::default()
        },
        hook_manager_params: Some(HookManagerParams {
            disable_acl_checker: true,
            ..Default::default()
        }),
        ..Default::default()
    }
}

/// Create an empty in-memory repo for tests.
///
/// This covers the simplest case.  For more complicated repositories, use
/// `TestRepoFactory`.
pub async fn build_empty<R>(fb: FacebookInit) -> Result<R>
where
    R: for<'builder> facet::AsyncBuildable<'builder, TestRepoFactoryBuilder<'builder>>,
{
    Ok(TestRepoFactory::new(fb)?.build().await?)
}

impl TestRepoFactory {
    /// Create a new factory for test repositories with default settings.
    pub fn new(fb: FacebookInit) -> Result<TestRepoFactory> {
        Self::with_sqlite_connection_callbacks(
            fb,
            SqliteConnection::open_in_memory()?,
            SqliteConnection::open_in_memory()?,
            None,
        )
    }
    /// Create a new factory for test repositories with an existing Sqlite
    /// connection.
    pub fn with_sqlite_connection(
        fb: FacebookInit,
        metadata_con: SqliteConnection,
        hg_mutation_con: SqliteConnection,
    ) -> Result<TestRepoFactory> {
        Self::with_sqlite_connection_callbacks(fb, metadata_con, hg_mutation_con, None)
    }

    /// Create a new factory for test repositories with an existing Sqlite
    /// connection.
    pub fn with_sqlite_connection_callbacks(
        fb: FacebookInit,
        metadata_con: SqliteConnection,
        hg_mutation_con: SqliteConnection,
        callbacks: Option<Box<dyn SqliteCallbacks>>,
    ) -> Result<TestRepoFactory> {
        metadata_con.execute_batch(MegarepoMapping::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlMutableCountersBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlBookmarksBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlChangesetsBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlBonsaiGitMappingBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlBonsaiGlobalrevMappingBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlBonsaiSvnrevMappingBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlBonsaiTagMappingBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlBonsaiHgMappingBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlPhasesBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlPushrebaseMutationMappingConnection::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlLongRunningRequestsQueue::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlMutableRenamesStore::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlSyncedCommitMapping::CREATION_QUERY)?;
        metadata_con.execute_batch(SegmentedChangelogSqlConnections::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlRepoLock::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlSparseProfilesSizes::CREATION_QUERY)?;
        metadata_con.execute_batch(StreamingCloneBuilder::CREATION_QUERY)?;
        metadata_con.execute_batch(SqlCommitGraphStorageBuilder::CREATION_QUERY)?;
        let metadata_db = SqlConnections::new_single(match callbacks {
            Some(callbacks) => Connection::with_sqlite_callbacks(metadata_con, callbacks),
            None => Connection::with_sqlite(metadata_con),
        });

        hg_mutation_con.execute_batch(SqlHgMutationStoreBuilder::CREATION_QUERY)?;
        let hg_mutation_db = SqlConnections::new_single(Connection::with_sqlite(hg_mutation_con));

        Ok(TestRepoFactory {
            fb,
            name: "repo".to_string(),
            config: default_test_repo_config(),
            blobstore: Arc::new(Memblob::default()),
            metadata_db,
            hg_mutation_db,
            redacted: None,
            permission_checker: None,
            derived_data_lease: None,
            filenodes_override: None,
        })
    }

    /// Get the metadata database this factory is using for repositories.
    pub fn metadata_db(&self) -> &SqlConnections {
        &self.metadata_db
    }

    /// Set the name for the next repository being built.
    pub fn with_name(&mut self, name: impl Into<String>) -> &mut Self {
        self.name = name.into();
        self
    }

    /// Set the ID for the next repository being built.
    pub fn with_id(&mut self, id: RepositoryId) -> &mut Self {
        self.config.repoid = id;
        self
    }

    /// Use a particular blobstore for repos built by this factory.
    pub fn with_blobstore(&mut self, blobstore: Arc<dyn Blobstore>) -> &mut Self {
        self.blobstore = blobstore;
        self
    }

    /// Redact content in repos that are built by this factory.
    pub fn redacted(&mut self, redacted: Option<RedactedBlobs>) -> &mut Self {
        self.redacted = redacted.map(Arc::new);
        self
    }

    /// Set a custom permission checker
    pub fn with_permission_checker(
        &mut self,
        permission_checker: ArcRepoPermissionChecker,
    ) -> &mut Self {
        self.permission_checker = Some(permission_checker);
        self
    }

    /// Modify the config of the repo.
    pub fn with_config_override(&mut self, modify: impl FnOnce(&mut RepoConfig)) -> &mut Self {
        modify(&mut self.config);
        self
    }

    /// Override the constructor for the derived data lease.
    pub fn with_derived_data_lease(
        &mut self,
        lease: impl Fn() -> Arc<dyn LeaseOps> + Send + Sync + 'static,
    ) -> &mut Self {
        self.derived_data_lease = Some(Box::new(lease));
        self
    }

    /// Override filenodes.
    pub fn with_filenodes_override(
        &mut self,
        filenodes_override: impl Fn(ArcFilenodes) -> ArcFilenodes + Send + Sync + 'static,
    ) -> &mut Self {
        self.filenodes_override = Some(Box::new(filenodes_override));
        self
    }

    /// Function to create megarepo mapping from the same connection as other DBs
    pub fn megarepo_mapping(&self) -> Arc<MegarepoMapping> {
        Arc::new(MegarepoMapping::from_sql_connections(
            self.metadata_db.clone(),
        ))
    }
}

#[facet::factory()]
impl TestRepoFactory {
    /// Construct RepoConfig based on the config in the factory.
    pub fn repo_config(&self) -> ArcRepoConfig {
        Arc::new(self.config.clone())
    }

    /// Construct RepoIdentity based on the config and name in the factory.
    pub fn repo_identity(&self, repo_config: &ArcRepoConfig) -> ArcRepoIdentity {
        Arc::new(RepoIdentity::new(repo_config.repoid, self.name.clone()))
    }

    /// Construct Changesets using the in-memory metadata database.
    pub fn changesets(
        &self,
        repo_identity: &ArcRepoIdentity,
        commit_graph: &ArcCommitGraph,
        repo_config: &ArcRepoConfig,
    ) -> Result<ArcChangesets> {
        let changesets = Arc::new(
            SqlChangesetsBuilder::from_sql_connections(self.metadata_db.clone())
                .build(RendezVousOptions::for_test(), repo_identity.id()),
        );

        Ok(Arc::new(ChangesetsCommitGraphCompat::new(
            self.fb,
            changesets,
            commit_graph.clone(),
            repo_identity.name().to_string(),
            repo_config.commit_graph_config.scuba_table.as_deref(),
        )?))
    }

    /// Construct a Changeset Fetcher.
    pub fn changeset_fetcher(
        &self,
        repo_identity: &ArcRepoIdentity,
        changesets: &ArcChangesets,
    ) -> ArcChangesetFetcher {
        Arc::new(SimpleChangesetFetcher::new(
            changesets.clone(),
            repo_identity.id(),
        ))
    }

    /// Construct SQL bookmarks using the in-memory metadata database.
    pub fn sql_bookmarks(&self, repo_identity: &ArcRepoIdentity) -> Result<ArcSqlBookmarks> {
        Ok(Arc::new(
            SqlBookmarksBuilder::from_sql_connections(self.metadata_db.clone())
                .with_repo_id(repo_identity.id()),
        ))
    }

    /// Construct Bookmarks.
    pub fn bookmarks(&self, sql_bookmarks: &ArcSqlBookmarks) -> ArcBookmarks {
        sql_bookmarks.clone()
    }

    /// Construct Bookmark update log.
    pub fn bookmark_update_log(&self, sql_bookmarks: &ArcSqlBookmarks) -> ArcBookmarkUpdateLog {
        sql_bookmarks.clone()
    }

    /// Construct Phases.
    pub fn phases(
        &self,
        repo_identity: &ArcRepoIdentity,
        bookmarks: &ArcBookmarks,
        changeset_fetcher: &ArcChangesetFetcher,
    ) -> ArcPhases {
        let sql_phases_builder = SqlPhasesBuilder::from_sql_connections(self.metadata_db.clone());
        let heads_fetcher = bookmark_heads_fetcher(bookmarks.clone());
        sql_phases_builder.build(repo_identity.id(), changeset_fetcher.clone(), heads_fetcher)
    }

    /// Construct Bonsai Hg Mapping using the in-memory metadata database.
    pub fn bonsai_hg_mapping(&self, repo_identity: &ArcRepoIdentity) -> Result<ArcBonsaiHgMapping> {
        Ok(Arc::new(
            SqlBonsaiHgMappingBuilder::from_sql_connections(self.metadata_db.clone())
                .build(repo_identity.id(), RendezVousOptions::for_test()),
        ))
    }

    /// Construct Bonsai Git Mapping using the in-memory metadata database.
    pub fn bonsai_git_mapping(
        &self,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiGitMapping> {
        Ok(Arc::new(
            SqlBonsaiGitMappingBuilder::from_sql_connections(self.metadata_db.clone())
                .build(repo_identity.id()),
        ))
    }

    /// Construct Bonsai Globalrev Mapping using the in-memory metadata
    /// database.
    pub fn bonsai_globalrev_mapping(
        &self,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiGlobalrevMapping> {
        Ok(Arc::new(
            SqlBonsaiGlobalrevMappingBuilder::from_sql_connections(self.metadata_db.clone())
                .build(repo_identity.id()),
        ))
    }

    /// Construct Bonsai Svnrev Mapping using the in-memory metadata
    /// database.
    pub fn bonsai_svnrev_mapping(
        &self,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiSvnrevMapping> {
        Ok(Arc::new(
            SqlBonsaiSvnrevMappingBuilder::from_sql_connections(self.metadata_db.clone())
                .build(repo_identity.id()),
        ))
    }

    /// Construct Bonsai Tag Mapping using the in-memory metadata
    /// database.
    pub fn bonsai_tag_mapping(
        &self,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcBonsaiTagMapping> {
        Ok(Arc::new(
            SqlBonsaiTagMappingBuilder::from_sql_connections(self.metadata_db.clone())
                .build(repo_identity.id()),
        ))
    }

    /// Construct Pushrebase Mutation Mapping using the in-memory metadata
    /// database.
    pub fn pushrebase_mutation_mapping(
        &self,
        repo_identity: &ArcRepoIdentity,
    ) -> Result<ArcPushrebaseMutationMapping> {
        Ok(Arc::new(
            SqlPushrebaseMutationMappingConnection::from_sql_connections(self.metadata_db.clone())
                .with_repo_id(repo_identity.id()),
        ))
    }

    /// Construct permission checker.  By default this allows all access.
    pub fn permission_checker(&self) -> Result<ArcRepoPermissionChecker> {
        if let Some(permission_checker) = &self.permission_checker {
            Ok(permission_checker.clone())
        } else {
            let permission_checker = AlwaysAllowRepoPermissionChecker::new();
            Ok(Arc::new(permission_checker))
        }
    }

    /// Construct Filenodes.
    ///
    /// Filenodes do not use the metadata db (each repo has its own filenodes
    /// db in memory).
    pub fn filenodes(&self, repo_identity: &ArcRepoIdentity) -> Result<ArcFilenodes> {
        let mut filenodes: ArcFilenodes =
            Arc::new(NewFilenodesBuilder::with_sqlite_in_memory()?.build(repo_identity.id()));
        if let Some(filenodes_override) = &self.filenodes_override {
            filenodes = filenodes_override(filenodes);
        }
        Ok(filenodes)
    }

    /// Construct Mercurial Mutation Store using the in-memory hg_mutation
    /// database.
    pub fn hg_mutation_store(&self, repo_identity: &ArcRepoIdentity) -> Result<ArcHgMutationStore> {
        Ok(Arc::new(
            SqlHgMutationStoreBuilder::from_sql_connections(self.hg_mutation_db.clone())
                .with_repo_id(repo_identity.id()),
        ))
    }

    /// Construct Segmented Changelog.  Segmented changelog is disabled in
    /// test repos.
    pub fn segmented_changelog(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        changeset_fetcher: &ArcChangesetFetcher,
        bookmarks: &ArcBookmarks,
    ) -> Result<ArcSegmentedChangelog> {
        new_test_segmented_changelog(
            CoreContext::test_mock(self.fb),
            repo_identity.id(),
            &repo_config.segmented_changelog_config,
            changeset_fetcher.clone(),
            bookmarks.clone(),
        )
    }

    /// Construct RepoDerivedData.  Derived data uses an in-process lease for
    /// tests.
    pub fn repo_derived_data(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        changesets: &ArcChangesets,
        commit_graph: &ArcCommitGraph,
        bonsai_hg_mapping: &ArcBonsaiHgMapping,
        filenodes: &ArcFilenodes,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcRepoDerivedData> {
        let lease = self.derived_data_lease.as_ref().map_or_else(
            || Arc::new(InProcessLease::new()) as Arc<dyn LeaseOps>,
            |lease| lease(),
        );
        Ok(Arc::new(RepoDerivedData::new(
            repo_identity.id(),
            repo_identity.name().to_string(),
            changesets.clone(),
            commit_graph.clone(),
            bonsai_hg_mapping.clone(),
            filenodes.clone(),
            repo_blobstore.as_ref().clone(),
            lease,
            MononokeScubaSampleBuilder::with_discard(),
            repo_config.derived_data_config.clone(),
            None, // derivation_service_client = None
        )?))
    }

    /// Construct the RepoBlobstore using the blobstore in the factory.
    pub fn repo_blobstore(&self, repo_identity: &ArcRepoIdentity) -> ArcRepoBlobstore {
        let repo_blobstore = RepoBlobstore::new(
            self.blobstore.clone(),
            self.redacted.clone(),
            repo_identity.id(),
            MononokeScubaSampleBuilder::with_discard(),
        );
        Arc::new(repo_blobstore)
    }

    /// Construct filestore config based on the config in the factory.
    pub fn filestore_config(&self, repo_config: &ArcRepoConfig) -> ArcFilestoreConfig {
        let filestore_config = repo_config.filestore.as_ref().map_or_else(
            FilestoreConfig::no_chunking_filestore,
            |p| FilestoreConfig {
                chunk_size: Some(p.chunk_size),
                concurrency: p.concurrency,
            },
        );
        Arc::new(filestore_config)
    }

    /// Disabled ephemeral repo
    pub fn repo_ephemeral_store(&self, repo_identity: &ArcRepoIdentity) -> ArcRepoEphemeralStore {
        Arc::new(RepoEphemeralStore::disabled(repo_identity.id()))
    }

    /// Mutable renames
    pub fn mutable_renames(&self, repo_identity: &ArcRepoIdentity) -> Result<ArcMutableRenames> {
        let sql_store = SqlMutableRenamesStore::from_sql_connections(self.metadata_db.clone());
        Ok(Arc::new(MutableRenames::new_test(
            repo_identity.id(),
            sql_store,
        )))
    }

    /// The commit mapping bettween repos for synced commits.
    pub fn synced_commit_mapping(&self) -> Result<ArcSyncedCommitMapping> {
        Ok(Arc::new(SqlSyncedCommitMapping::from_sql_connections(
            self.metadata_db.clone(),
        )))
    }

    /// Cross-repo sync manager for this repo
    pub fn repo_cross_repo(
        &self,
        synced_commit_mapping: &ArcSyncedCommitMapping,
    ) -> Result<ArcRepoCrossRepo> {
        let live_commit_sync_config = Arc::new(TestLiveCommitSyncConfig::new_empty());
        let sync_lease = Arc::new(InProcessLease::new());
        Ok(Arc::new(RepoCrossRepo::new(
            synced_commit_mapping.clone(),
            live_commit_sync_config,
            sync_lease,
        )))
    }

    /// Test repo-handler-base
    pub fn repo_handler_base(
        &self,
        repo_config: &ArcRepoConfig,
        repo_cross_repo: &ArcRepoCrossRepo,
        repo_identity: &ArcRepoIdentity,
        bookmarks: &ArcBookmarks,
        bookmark_update_log: &ArcBookmarkUpdateLog,
        mutable_counters: &ArcMutableCounters,
    ) -> Result<ArcRepoHandlerBase> {
        let ctx = CoreContext::test_mock(self.fb);
        let scuba = ctx.scuba().clone();
        let logger = ctx.logger().clone();
        let repo_client_knobs = repo_config.repo_client_knobs.clone();

        let common_commit_sync_config = repo_cross_repo
            .live_commit_sync_config()
            .clone()
            .get_common_config_if_exists(repo_identity.id())?;
        let synced_commit_mapping = repo_cross_repo.synced_commit_mapping();
        let backup_repo_config = repo_config.backup_repo_config.clone();
        let target_repo_dbs = Arc::new(TargetRepoDbs {
            bookmarks: bookmarks.clone(),
            bookmark_update_log: bookmark_update_log.clone(),
            counters: mutable_counters.clone(),
        });

        let maybe_push_redirector_base =
            common_commit_sync_config.map(|common_commit_sync_config| {
                Arc::new(PushRedirectorBase {
                    common_commit_sync_config,
                    target_repo_dbs,
                    synced_commit_mapping: synced_commit_mapping.clone(),
                })
            });
        Ok(Arc::new(RepoHandlerBase {
            logger,
            scuba,
            repo_client_knobs,
            maybe_push_redirector_base,
            backup_repo_config,
        }))
    }

    /// Mutable counters
    pub fn mutable_counters(&self, repo_identity: &ArcRepoIdentity) -> Result<ArcMutableCounters> {
        Ok(Arc::new(
            SqlMutableCountersBuilder::from_sql_connections(self.metadata_db.clone())
                .build(repo_identity.id()),
        ))
    }

    /// Set of DerivedDataManagers for DDS
    pub fn derived_data_manager_set(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_config: &ArcRepoConfig,
        changesets: &ArcChangesets,
        commit_graph: &ArcCommitGraph,
        bonsai_hg_mapping: &ArcBonsaiHgMapping,
        filenodes: &ArcFilenodes,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> Result<ArcDerivedDataManagerSet> {
        let config = repo_config.derived_data_config.clone();
        let lease = self.derived_data_lease.as_ref().map_or_else(
            || Arc::new(InProcessLease::new()) as Arc<dyn LeaseOps>,
            |lease| lease(),
        );
        let ctx = CoreContext::test_mock(self.fb);
        let logger = ctx.logger().clone();
        anyhow::Ok(Arc::new(DerivedDataManagerSet::new(
            repo_identity.id(),
            repo_identity.name().to_string(),
            changesets.clone(),
            commit_graph.clone(),
            bonsai_hg_mapping.clone(),
            filenodes.clone(),
            repo_blobstore.as_ref().clone(),
            lease,
            logger,
            MononokeScubaSampleBuilder::with_discard(),
            config,
            None, // derivation_service_client = None
        )?))
    }

    /// ACL regions
    pub fn acl_regions(
        &self,
        repo_config: &ArcRepoConfig,
        commit_graph: &ArcCommitGraph,
    ) -> ArcAclRegions {
        build_acl_regions(repo_config.acl_region_config.as_ref(), commit_graph.clone())
    }

    /// Hook manager
    pub fn hook_manager(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_derived_data: &ArcRepoDerivedData,
        bookmarks: &ArcBookmarks,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> ArcHookManager {
        let content_store = RepoFileContentManager::from_parts(
            bookmarks.clone(),
            repo_blobstore.clone(),
            repo_derived_data.clone(),
        );

        Arc::new(HookManager::new_test(
            repo_identity.name().to_string(),
            Box::new(content_store),
        ))
    }

    /// Sparse profiles
    pub fn sparse_profile(&self, _repo_config: &ArcRepoConfig) -> ArcRepoSparseProfiles {
        Arc::new(RepoSparseProfiles {
            sql_profile_sizes: None,
        })
    }

    /// Construct unlocked repo lock.
    pub fn repo_lock(&self, repo_identity: &ArcRepoIdentity) -> Result<ArcRepoLock> {
        let repo_lock = AlwaysUnlockedRepoLock::new(repo_identity.id());
        Ok(Arc::new(repo_lock))
    }

    /// Repo bookmark attrs
    pub fn repo_bookmark_attrs(&self, repo_config: &ArcRepoConfig) -> Result<ArcRepoBookmarkAttrs> {
        Ok(Arc::new(RepoBookmarkAttrs::new_test(
            repo_config.bookmarks.clone(),
        )?))
    }

    /// Streaming clone
    pub fn streaming_clone(
        &self,
        repo_identity: &ArcRepoIdentity,
        repo_blobstore: &ArcRepoBlobstore,
    ) -> ArcStreamingClone {
        Arc::new(
            StreamingCloneBuilder::from_sql_connections(self.metadata_db.clone())
                .build(repo_identity.id(), repo_blobstore.clone()),
        )
    }

    /// Sql query config
    pub fn sql_query_config(&self) -> ArcSqlQueryConfig {
        Arc::new(SqlQueryConfig { caching: None })
    }

    /// Commit graph
    pub fn commit_graph(&self, repo_identity: &RepoIdentity) -> Result<ArcCommitGraph> {
        let sql_storage =
            SqlCommitGraphStorageBuilder::from_sql_connections(self.metadata_db.clone())
                .build(RendezVousOptions::for_test(), repo_identity.id());
        Ok(Arc::new(CommitGraph::new(Arc::new(sql_storage))))
    }

    /// Warm bookmarks cache
    pub async fn warm_bookmarks_cache(
        &self,
        repo_identity: &ArcRepoIdentity,
        bookmarks: &ArcBookmarks,
        bookmark_update_log: &ArcBookmarkUpdateLog,
        repo_derived_data: &ArcRepoDerivedData,
        phases: &ArcPhases,
    ) -> Result<ArcBookmarksCache> {
        let ctx = CoreContext::test_mock(self.fb);
        let mut warm_bookmarks_cache_builder = WarmBookmarksCacheBuilder::new(
            ctx,
            bookmarks.clone(),
            bookmark_update_log.clone(),
            repo_identity.clone(),
        );
        warm_bookmarks_cache_builder.add_all_warmers(repo_derived_data, phases)?;
        warm_bookmarks_cache_builder.wait_until_warmed();
        Ok(Arc::new(warm_bookmarks_cache_builder.build().await?))
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::num::NonZeroUsize;
use std::time::Duration;

use clap::Parser;
use clap::Subcommand;
use mononoke_app::args::RepoArgs;
use mononoke_types::DateTime;

use crate::ImportStage;
use crate::RecoveryFields;

#[derive(Parser)]
#[clap(about = "Check for additional setup steps before running the repo_import tool")]
pub struct CheckAdditionalSetupStepsArgs {
    /// Disable waiting for Phabricator to parse commits.
    #[clap(long)]
    pub disable_phabricator_check: bool,
    /// Suffix of the bookmark (repo_import_<suffix>). \
    //  This bookmark is used to publish the imported commits and to track the parsing of commits on Phabricator.
    #[clap(long)]
    pub bookmark_suffix: String,
    ///The bookmark branch we want to merge our repo into (e.g. master)
    #[clap(long)]
    pub dest_bookmark: String,
}

//import
#[derive(Parser)]
#[clap(about = "Run the whole repo_import process")]
pub struct ImportArgs {
    /// File path to fetch the recovery state for repo_import tool.
    pub git_repository_path: String,
    /// Revision in a git repo which should be merged
    #[clap(long)]
    pub git_merge_rev_id: String,
    ///Path to the destination folder we import to
    #[clap(long)]
    pub dest_path: String,
    ///Number of commits we make visible when moving the bookmark
    #[clap(long, default_value_t = NonZeroUsize::new(100).unwrap())]
    pub batch_size: NonZeroUsize,
    #[clap(flatten)]
    pub additional_setup_steps_args: CheckAdditionalSetupStepsArgs,
    ///Disable x_repo sync check after moving the bookmark
    #[clap(long)]
    pub disable_x_repo_check: bool,
    ///Disable hg sync check after moving the bookmark
    #[clap(long)]
    pub disable_hg_sync_check: bool,
    ///Sleep time in seconds, if we fail dependent system (phabricator, hg_sync ...) checkers
    #[clap(long, default_value_t = 5)]
    pub sleep_time_secs: u64,
    ///commit author to use
    #[clap(long)]
    pub commit_author: String,
    ///commit message to use
    #[clap(long)]
    pub commit_message: String,
    ///commit date to use (default is now)
    #[clap(long, value_parser = DateTime::from_rfc3339)]
    pub commit_date_rfc3339: Option<DateTime>,
    ///File path to store the importing state for recovery in case the tool breaks
    #[clap(long)]
    pub recovery_file_path: String,
}

//recover-process
#[derive(Parser)]
#[clap(about = "Repo_import tool process recovery in case of import failure")]
pub struct RecoverProcessArgs {
    /// Path to a git repository to import
    pub saved_recovery_file_path: String,
}

#[derive(Subcommand)]
pub enum Commands {
    CheckAdditionalSetupSteps(CheckAdditionalSetupStepsArgs),
    Import(ImportArgs),
    RecoverProcess(RecoverProcessArgs),
}

#[derive(Parser)]
#[clap(about = "Automating repository imports")]
pub struct MononokeRepoImportArgs {
    /// The repository name or ID
    #[clap(flatten)]
    pub repo: RepoArgs,
    /// Error out before the actual merge step. Useful for running all the a
    /// earlier steps to prepare for merge time.
    #[clap(long)]
    pub no_merge: bool,
    #[clap(subcommand)]
    pub command: Option<Commands>,
}

pub fn setup_import_args(import_args: ImportArgs) -> RecoveryFields {
    RecoveryFields {
        import_stage: ImportStage::GitImport,
        recovery_file_path: import_args.recovery_file_path,
        git_merge_rev_id: import_args.git_merge_rev_id,
        git_repo_path: import_args.git_repository_path,
        dest_path: import_args.dest_path,
        bookmark_suffix: import_args.additional_setup_steps_args.bookmark_suffix,
        batch_size: import_args.batch_size.get(),
        move_bookmark_commits_done: 0,
        phab_check_disabled: import_args
            .additional_setup_steps_args
            .disable_phabricator_check,
        x_repo_check_disabled: import_args.disable_x_repo_check,
        hg_sync_check_disabled: import_args.disable_hg_sync_check,
        sleep_time: Duration::from_secs(import_args.sleep_time_secs),
        dest_bookmark_name: import_args.additional_setup_steps_args.dest_bookmark,
        commit_author: import_args.commit_author,
        commit_message: import_args.commit_message,
        datetime: import_args
            .commit_date_rfc3339
            .unwrap_or_else(DateTime::now),
        imported_cs_id: None,
        shifted_bcs_ids: None,
        gitimport_bcs_ids: None,
        merged_cs_id: None,
        git_merge_bcs_id: None,
    }
}