/*
 * 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::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration;

use anyhow::format_err;
use anyhow::Context;
use anyhow::Error;
use blobrepo::BlobRepo;
use blobrepo_hg::save_bonsai_changeset_object;
use blobrepo_hg::BlobRepoHg;
use blobrepo_hg::ChangesetHandle;
use blobstore::Blobstore;
use blobstore::Loadable;
use blobstore::LoadableError;
use bonsai_hg_mapping::BonsaiHgMappingRef;
use bookmarks::BookmarkKey;
use bookmarks::Freshness;
use bytes::Bytes;
use changeset_fetcher::ChangesetFetcherRef;
use changesets::ChangesetInsert;
use changesets::ChangesetsRef;
use commit_graph::CommitGraphRef;
use context::CoreContext;
use edenapi_types::AnyId;
use edenapi_types::UploadToken;
use ephemeral_blobstore::Bubble;
use ephemeral_blobstore::BubbleId;
use ephemeral_blobstore::RepoEphemeralStore;
use ephemeral_blobstore::StorageLocation;
use filestore::FetchKey;
use filestore::FilestoreConfigRef;
use filestore::StoreRequest;
use futures::compat::Future01CompatExt;
use futures::compat::Stream01CompatExt;
use futures::future;
use futures::stream;
use futures::Stream;
use futures::StreamExt;
use futures::TryStream;
use futures::TryStreamExt;
use futures_util::try_join;
use hgproto::GettreepackArgs;
use mercurial_derivation::DeriveHgChangeset;
use mercurial_mutation::HgMutationEntry;
use mercurial_mutation::HgMutationStoreRef;
use mercurial_types::blobs::RevlogChangeset;
use mercurial_types::blobs::UploadHgNodeHash;
use mercurial_types::blobs::UploadHgTreeEntry;
use mercurial_types::HgChangesetId;
use mercurial_types::HgFileEnvelopeMut;
use mercurial_types::HgFileNodeId;
use mercurial_types::HgManifestId;
use mercurial_types::HgNodeHash;
use metaconfig_types::RepoConfig;
use mononoke_api::errors::MononokeError;
use mononoke_api::path::MononokePath;
use mononoke_api::repo::RepoContext;
use mononoke_types::BonsaiChangeset;
use mononoke_types::ChangesetId;
use mononoke_types::ContentId;
use mononoke_types::ContentMetadataV2;
use mononoke_types::MPath;
use mononoke_types::RepoPath;
use phases::PhasesRef;
use repo_blobstore::RepoBlobstore;
use repo_blobstore::RepoBlobstoreRef;
use repo_client::find_new_draft_commits_and_derive_filenodes_for_public_roots;
use repo_client::gettreepack_entries;
use repo_update_logger::log_new_commits;
use repo_update_logger::CommitInfo;
use segmented_changelog::CloneData;
use segmented_changelog::Location;
use slog::debug;
use unbundle::upload_changeset;

use super::HgFileContext;
use super::HgTreeContext;

#[derive(Clone)]
pub struct HgRepoContext {
    repo: RepoContext,
}

pub struct HgChangesetSegment {
    pub head: HgChangesetId,
    pub base: HgChangesetId,
    pub length: u64,
    pub parents: Vec<HgChangesetSegmentParent>,
}

pub struct HgChangesetSegmentParent {
    pub hgid: HgChangesetId,
    pub location: Option<Location<HgChangesetId>>,
}

impl HgRepoContext {
    pub(crate) fn new(repo: RepoContext) -> Self {
        Self { repo }
    }

    /// The `CoreContext` for this query.
    pub fn ctx(&self) -> &CoreContext {
        self.repo.ctx()
    }

    /// The `RepoContext` for this query.
    pub fn repo(&self) -> &RepoContext {
        &self.repo
    }

    /// The underlying Mononoke `BlobRepo` backing this repo.
    pub(crate) fn blob_repo(&self) -> &BlobRepo {
        self.repo().blob_repo()
    }

    /// The configuration for the repository.
    pub(crate) fn config(&self) -> &RepoConfig {
        self.repo.config()
    }

    /// Create bubble and return its id
    pub async fn create_bubble(
        &self,
        custom_duration: Option<Duration>,
        labels: Vec<String>,
    ) -> Result<Bubble, MononokeError> {
        Ok(self
            .repo()
            .repo_ephemeral_store_arc()
            .create_bubble(custom_duration, labels)
            .await?)
    }

    pub fn ephemeral_store(&self) -> Arc<RepoEphemeralStore> {
        self.repo().repo_ephemeral_store_arc()
    }

    /// Load bubble from id
    pub async fn open_bubble(&self, bubble_id: BubbleId) -> Result<Bubble, MononokeError> {
        self.repo.open_bubble(bubble_id).await
    }

    /// Get blobstore. If bubble id is present, this is the ephemeral blobstore
    pub async fn bubble_blobstore(
        &self,
        bubble_id: Option<BubbleId>,
    ) -> Result<RepoBlobstore, MononokeError> {
        let main_blobstore = self.blob_repo().repo_blobstore().clone();
        Ok(match bubble_id {
            Some(id) => self
                .repo
                .open_bubble(id)
                .await?
                .wrap_repo_blobstore(main_blobstore),
            None => main_blobstore,
        })
    }

    /// Get changeset id from hg changeset id
    pub async fn get_bonsai_from_hg(
        &self,
        hgid: HgChangesetId,
    ) -> Result<Option<ChangesetId>, MononokeError> {
        Ok(self
            .blob_repo()
            .bonsai_hg_mapping()
            .get_bonsai_from_hg(self.ctx(), hgid)
            .await?)
    }

    /// Fetch file content size, fails if it doesn't exist.
    pub async fn fetch_file_content_size(
        &self,
        content_id: ContentId,
        bubble_id: Option<BubbleId>,
    ) -> Result<u64, MononokeError> {
        Ok(filestore::get_metadata(
            &self.bubble_blobstore(bubble_id).await?,
            self.ctx(),
            &FetchKey::Canonical(content_id),
        )
        .await?
        .ok_or_else(|| {
            MononokeError::InvalidRequest(format!(
                "failed to fetch or rebuild metadata for ContentId('{}'), file content must be prior uploaded",
                content_id
            ))
        })?
        .total_size)
    }

    async fn is_key_present_in_blobstore(
        &self,
        key: &str,
        bubble_id: Option<BubbleId>,
    ) -> Result<bool, MononokeError> {
        async move {
            self.bubble_blobstore(bubble_id)
                .await?
                .is_present(self.ctx(), key)
                .await
                .map(|is_present| {
                    // if we can't resolve the presence (some blobstores failed, some returned None)
                    // we can re-upload the blob
                    is_present.assume_not_found_if_unsure()
                })
        }
        .await
        .map_err(MononokeError::from)
    }

    /// Look up in blobstore by `ContentId`
    pub async fn is_file_present(
        &self,
        hash: impl Into<FetchKey>,
        bubble_id: Option<BubbleId>,
    ) -> Result<bool, MononokeError> {
        self.is_key_present_in_blobstore(&hash.into().blobstore_key(), bubble_id)
            .await
    }

    /// Convert given hash to canonical ContentId
    pub async fn convert_file_to_content_id<H: Into<FetchKey> + Copy + std::fmt::Debug>(
        &self,
        hash: H,
        bubble_id: Option<BubbleId>,
    ) -> Result<Option<ContentId>, MononokeError> {
        match hash
            .into()
            .load(self.ctx(), &self.bubble_blobstore(bubble_id).await?)
            .await
        {
            Ok(cid) => Ok(Some(cid)),
            Err(LoadableError::Missing(_)) => Ok(None),
            Err(LoadableError::Error(err)) => {
                Err(err).with_context(|| format_err!("While fetching ContentId for {:?}", hash))?
            }
        }
    }

    /// Store file into blobstore
    pub async fn store_file(
        &self,
        key: impl Into<FetchKey>,
        size: u64,
        data: impl Stream<Item = Result<Bytes, Error>> + Send,
        bubble_id: Option<BubbleId>,
    ) -> Result<ContentMetadataV2, MononokeError> {
        filestore::store(
            &self.bubble_blobstore(bubble_id).await?,
            *self.blob_repo().filestore_config(),
            self.ctx(),
            &StoreRequest::with_fetch_key(size, key.into()),
            data,
        )
        .await
        .map_err(MononokeError::from)
    }

    /// Download file contents
    pub async fn download_file(
        &self,
        upload_token: UploadToken,
    ) -> Result<Option<impl Stream<Item = Result<Bytes, Error>> + 'static>, MononokeError> {
        Ok(filestore::fetch(
            self.bubble_blobstore(upload_token.data.bubble_id.map(BubbleId::new))
                .await?,
            self.ctx().clone(),
            &match upload_token.data.id {
                AnyId::AnyFileContentId(file_id) => file_id.into(),
                e => {
                    return Err(MononokeError::from(format_err!(
                        "Id is not of a file: {:?}",
                        e
                    )));
                }
            },
        )
        .await?)
    }

    /// Test whether a Mercurial changeset exists.
    pub async fn hg_changeset_exists(
        &self,
        hg_changeset_id: HgChangesetId,
    ) -> Result<bool, MononokeError> {
        self.blob_repo()
            .hg_changeset_exists(self.ctx().clone(), hg_changeset_id)
            .await
            .map_err(MononokeError::from)
    }

    /// Test whether a changeset exists in a particular storage location.
    pub async fn changeset_exists(
        &self,
        changeset_id: ChangesetId,
        storage_location: StorageLocation,
    ) -> Result<bool, MononokeError> {
        self.repo
            .changeset_exists(changeset_id, storage_location)
            .await
    }

    /// Look up in blobstore by `HgFileNodeId`
    pub async fn filenode_exists(&self, filenode_id: HgFileNodeId) -> Result<bool, MononokeError> {
        self.is_key_present_in_blobstore(&filenode_id.blobstore_key(), None)
            .await
    }

    /// Look up in blobstore by `HgManifestId`
    pub async fn tree_exists(&self, manifest_id: HgManifestId) -> Result<bool, MononokeError> {
        self.is_key_present_in_blobstore(&manifest_id.blobstore_key(), None)
            .await
    }

    /// Look up a file in the repo by `HgFileNodeId`.
    pub async fn file(
        &self,
        filenode_id: HgFileNodeId,
    ) -> Result<Option<HgFileContext>, MononokeError> {
        HgFileContext::new_check_exists(self.clone(), filenode_id).await
    }

    /// Look up a tree in the repo by `HgManifestId`.
    pub async fn tree(
        &self,
        manifest_id: HgManifestId,
    ) -> Result<Option<HgTreeContext>, MononokeError> {
        HgTreeContext::new_check_exists(self.clone(), manifest_id).await
    }

    /// Store HgFilenode into blobstore
    pub async fn store_hg_filenode(
        &self,
        filenode_id: HgFileNodeId,
        p1: Option<HgFileNodeId>,
        p2: Option<HgFileNodeId>,
        content_id: ContentId,
        content_size: u64,
        metadata: Bytes,
    ) -> Result<(), MononokeError> {
        let envelope = HgFileEnvelopeMut {
            node_id: filenode_id,
            p1,
            p2,
            content_id,
            content_size,
            metadata,
        };

        self.blob_repo()
            .repo_blobstore()
            .put(
                self.ctx(),
                filenode_id.blobstore_key(),
                envelope.freeze().into_blob().into(),
            )
            .await
            .map_err(MononokeError::from)?;
        Ok(())
    }

    /// Store Tree into blobstore
    pub async fn store_tree(
        &self,
        upload_node_id: HgNodeHash,
        p1: Option<HgNodeHash>,
        p2: Option<HgNodeHash>,
        contents: Bytes,
    ) -> Result<(), MononokeError> {
        let entry = UploadHgTreeEntry {
            upload_node_id: UploadHgNodeHash::Checked(upload_node_id),
            contents,
            p1,
            p2,
            path: RepoPath::RootPath, // only used for logging
        };
        let (_, upload_future) = entry.upload(
            self.ctx().clone(),
            Arc::new(self.blob_repo().repo_blobstore().clone()),
        )?;

        upload_future.compat().await.map_err(MononokeError::from)?;

        Ok(())
    }

    /// Store HgChangeset. The function also generates bonsai changeset and stores all necessary mappings.
    pub async fn store_hg_changesets(
        &self,
        changesets: Vec<(HgChangesetId, RevlogChangeset)>,
        mutations: Vec<HgMutationEntry>,
    ) -> Result<Vec<Result<(HgChangesetId, BonsaiChangeset), MononokeError>>, MononokeError> {
        let mut uploaded_changesets: HashMap<HgChangesetId, ChangesetHandle> = HashMap::new();
        let filelogs = HashMap::new();
        let manifests = HashMap::new();
        for (node, revlog_cs) in changesets {
            uploaded_changesets = upload_changeset(
                self.ctx().clone(),
                self.blob_repo().clone(),
                self.ctx().scuba().clone(),
                node,
                &revlog_cs,
                uploaded_changesets,
                &filelogs,
                &manifests,
                None, /* maybe_backup_repo_source (unsupported here) */
            )
            .await
            .map_err(MononokeError::from)?;
        }
        let mut results = Vec::new();
        let mut hg_changesets = HashSet::new();
        let mut commits_to_log = Vec::new();
        for (hg_cs_id, handle) in uploaded_changesets {
            let result = match handle.get_completed_changeset().await {
                Ok((bonsai, _)) => {
                    hg_changesets.insert(hg_cs_id);
                    commits_to_log.push(CommitInfo::new(&bonsai, None));
                    Ok((hg_cs_id, bonsai))
                }
                Err(e) => Err(MononokeError::from(Error::from(e))),
            };
            results.push(result);
        }
        log_new_commits(self.ctx(), self.repo().inner_repo(), None, commits_to_log).await;
        self.blob_repo()
            .hg_mutation_store()
            .add_entries(self.ctx(), hg_changesets, mutations)
            .await
            .map_err(MononokeError::from)?;

        Ok(results)
    }

    pub async fn fetch_mutations(
        &self,
        hg_changesets: HashSet<HgChangesetId>,
    ) -> Result<Vec<HgMutationEntry>, MononokeError> {
        Ok(self
            .blob_repo()
            .hg_mutation_store()
            .all_predecessors(self.ctx(), hg_changesets)
            .await?)
    }

    /// Store bonsai changeset
    pub async fn store_bonsai_changeset(
        &self,
        bonsai_cs: BonsaiChangeset,
    ) -> Result<(), MononokeError> {
        let blobstore = self.blob_repo().repo_blobstore();
        let cs_id = bonsai_cs.get_changeset_id();
        let insert = ChangesetInsert {
            cs_id,
            parents: bonsai_cs.parents().collect(),
        };
        match save_bonsai_changeset_object(self.ctx(), blobstore, bonsai_cs).await {
            Ok(_) => self.blob_repo().changesets().add(self.ctx(), insert).await,
            Err(err) => Err(err),
        }?;

        Ok(())
    }

    /// Request all of the tree nodes in the repo under a given path.
    ///
    /// The caller must specify a list of desired versions of the subtree for
    /// this path, specified as a list of manifest IDs of tree nodes
    /// corresponding to different versions of the root node of the subtree.
    ///
    /// The caller may also specify a list of versions of the subtree to
    /// delta against. The server will only return tree nodes that are in
    /// the requested subtrees that are not in the base subtrees.
    ///
    /// Returns a stream of `HgTreeContext`s, each corresponding to a node in
    /// the requested versions of the subtree, along with its associated path.
    ///
    /// This method is equivalent to Mercurial's `gettreepack` wire protocol
    /// command.
    pub fn trees_under_path(
        &self,
        path: MononokePath,
        root_versions: impl IntoIterator<Item = HgManifestId>,
        base_versions: impl IntoIterator<Item = HgManifestId>,
        depth: Option<usize>,
    ) -> impl TryStream<Ok = (HgTreeContext, MononokePath), Error = MononokeError> {
        let ctx = self.ctx().clone();
        let blob_repo = self.blob_repo();
        let args = GettreepackArgs {
            rootdir: path.into_mpath(),
            mfnodes: root_versions.into_iter().collect(),
            basemfnodes: base_versions.into_iter().collect(),
            directories: vec![], // Not supported.
            depth,
        };

        gettreepack_entries(ctx, blob_repo, args)
            .compat()
            .map_err(MononokeError::from)
            .and_then({
                let repo = self.clone();
                move |(mfid, path): (HgManifestId, Option<MPath>)| {
                    let repo = repo.clone();
                    async move {
                        let tree = HgTreeContext::new(repo, mfid).await?;
                        let path = MononokePath::new(path);
                        Ok((tree, path))
                    }
                }
            })
    }

    // TODO(mbthomas): get_hg_from_bonsai -> derive_hg_changeset
    pub async fn get_hg_from_bonsai(
        &self,
        cs_id: ChangesetId,
    ) -> Result<HgChangesetId, MononokeError> {
        Ok(self
            .blob_repo()
            .derive_hg_changeset(self.ctx(), cs_id)
            .await?)
    }

    /// This provides the same functionality as
    /// `mononoke_api::RepoContext::location_to_changeset_id`. It just wraps the request and
    /// response using Mercurial specific types.
    pub async fn location_to_hg_changeset_id(
        &self,
        location: Location<HgChangesetId>,
        count: u64,
    ) -> Result<Vec<HgChangesetId>, MononokeError> {
        let cs_location = location
            .and_then_descendant(|descendant| async move {
                self.blob_repo()
                    .bonsai_hg_mapping()
                    .get_bonsai_from_hg(self.ctx(), descendant)
                    .await?
                    .ok_or_else(|| {
                        MononokeError::InvalidRequest(format!(
                            "hg changeset {} not found",
                            location.descendant
                        ))
                    })
            })
            .await?;
        let result_csids = self
            .repo()
            .location_to_changeset_id(cs_location, count)
            .await?;
        let hg_id_futures = result_csids.iter().map(|result_csid| {
            self.blob_repo()
                .derive_hg_changeset(self.ctx(), *result_csid)
        });
        future::try_join_all(hg_id_futures)
            .await
            .map_err(MononokeError::from)
    }

    /// This provides the same functionality as
    /// `mononke_api::RepoContext::many_changeset_ids_to_locations`. It just translates to
    /// and from Mercurial types.
    pub async fn many_changeset_ids_to_locations(
        &self,
        hg_master_heads: Vec<HgChangesetId>,
        hg_ids: Vec<HgChangesetId>,
    ) -> Result<HashMap<HgChangesetId, Result<Location<HgChangesetId>, MononokeError>>, MononokeError>
    {
        let all_hg_ids: Vec<_> = hg_ids
            .iter()
            .cloned()
            .chain(hg_master_heads.clone().into_iter())
            .collect();
        let hg_to_bonsai: HashMap<HgChangesetId, ChangesetId> = self
            .blob_repo()
            .get_hg_bonsai_mapping(self.ctx().clone(), all_hg_ids)
            .await?
            .into_iter()
            .collect();
        let master_heads = hg_master_heads
            .iter()
            .map(|master_id| {
                hg_to_bonsai.get(master_id).cloned().ok_or_else(|| {
                    MononokeError::InvalidRequest(format!(
                        "failed to find bonsai equivalent for client head {}",
                        master_id
                    ))
                })
            })
            .collect::<Result<Vec<_>, MononokeError>>()?;

        // We should treat hg_ids as being absolutely any hash. It is perfectly valid for the
        // server to have not encountered the hash that it was given to convert. Filter out the
        // hashes that we could not convert to bonsai.
        let cs_ids = hg_ids
            .iter()
            .filter_map(|hg_id| hg_to_bonsai.get(hg_id).cloned())
            .collect::<Vec<ChangesetId>>();

        let cs_to_blocations = self
            .repo()
            .many_changeset_ids_to_locations(master_heads, cs_ids)
            .await?;

        let bonsai_to_hg: HashMap<ChangesetId, HgChangesetId> = self
            .blob_repo()
            .get_hg_bonsai_mapping(
                self.ctx().clone(),
                cs_to_blocations
                    .iter()
                    .filter_map(|(_, result)| match result {
                        Ok(l) => Some(l.descendant),
                        _ => None,
                    })
                    .collect::<Vec<_>>(),
            )
            .await?
            .into_iter()
            .map(|(hg_id, cs_id)| (cs_id, hg_id))
            .collect();
        let response = hg_ids
            .into_iter()
            .filter_map(|hg_id| hg_to_bonsai.get(&hg_id).map(|cs_id| (hg_id, cs_id)))
            .filter_map(|(hg_id, cs_id)| {
                cs_to_blocations
                    .get(cs_id)
                    .map(|cs_result| (hg_id, cs_result.clone()))
            })
            .map(|(hg_id, cs_result)| {
                let cs_result = match cs_result {
                    Ok(cs_location) => cs_location.try_map_descendant(|descendant| {
                        bonsai_to_hg.get(&descendant).cloned().ok_or_else(|| {
                            MononokeError::InvalidRequest(format!(
                                "failed to find hg equivalent for bonsai {}",
                                descendant
                            ))
                        })
                    }),
                    Err(e) => Err(e),
                };
                (hg_id, cs_result)
            })
            .collect::<HashMap<HgChangesetId, Result<Location<HgChangesetId>, MononokeError>>>();

        Ok(response)
    }

    pub async fn revlog_commit_data(
        &self,
        hg_cs_id: HgChangesetId,
    ) -> Result<Option<Bytes>, MononokeError> {
        let ctx = self.ctx();
        let blobstore = self.blob_repo().repo_blobstore();
        let revlog_cs = RevlogChangeset::load(ctx, blobstore, hg_cs_id)
            .await
            .map_err(MononokeError::from)?;
        let revlog_cs = match revlog_cs {
            None => return Ok(None),
            Some(x) => x,
        };

        let mut buffer = Vec::new();
        revlog_cs
            .generate_for_hash_verification(&mut buffer)
            .map_err(MononokeError::from)?;
        Ok(Some(buffer.into()))
    }

    pub async fn segmented_changelog_clone_data(
        &self,
    ) -> Result<CloneData<HgChangesetId>, MononokeError> {
        let (m_clone_data, hints) = self.repo().segmented_changelog_clone_data().await?;
        self.convert_clone_data(m_clone_data, hints).await
    }

    pub async fn segmented_changelog_disabled(&self) -> Result<bool, MononokeError> {
        self.repo().segmented_changelog_disabled().await
    }

    pub async fn segmented_changelog_pull_data(
        &self,
        common: Vec<HgChangesetId>,
        missing: Vec<HgChangesetId>,
    ) -> Result<CloneData<HgChangesetId>, MononokeError> {
        let input_hgids = common
            .iter()
            .chain(missing.iter())
            .cloned()
            .collect::<Vec<_>>();
        let hg_to_bonsai: HashMap<HgChangesetId, ChangesetId> = self
            .blob_repo()
            .get_hg_bonsai_mapping(self.ctx().clone(), input_hgids)
            .await?
            .into_iter()
            .collect();
        let common = common
            .into_iter()
            .map(|hgid| {
                hg_to_bonsai
                    .get(&hgid)
                    .copied()
                    .ok_or_else(|| format_err!("Failed to convert common {} to bonsai", hgid))
            })
            .collect::<Result<Vec<_>, _>>()?;
        let missing = missing
            .into_iter()
            .map(|hgid| {
                hg_to_bonsai
                    .get(&hgid)
                    .copied()
                    .ok_or_else(|| format_err!("Failed to convert missing {} to bonsai", hgid))
            })
            .collect::<Result<Vec<_>, _>>()?;
        let m_clone_data = self
            .repo()
            .segmented_changelog_pull_data(common, missing)
            .await?;
        self.convert_clone_data(m_clone_data, HashMap::new()).await
    }

    async fn convert_clone_data(
        &self,
        m_clone_data: CloneData<ChangesetId>,
        hints: HashMap<ChangesetId, HgChangesetId>,
    ) -> Result<CloneData<HgChangesetId>, MononokeError> {
        let mapping = {
            let to_fetch: Vec<ChangesetId> = m_clone_data
                .idmap
                .values()
                .filter(|csid| !hints.contains_key(csid))
                .copied()
                .collect();

            let mut mapping = hints;

            if !to_fetch.is_empty() {
                self.blob_repo()
                    .get_hg_bonsai_mapping(self.ctx().clone(), to_fetch)
                    .await
                    .context("error fetching hg bonsai mapping")?
                    .into_iter()
                    .fold(&mut mapping, |mapping, (hgid, csid)| {
                        mapping.insert(csid, hgid);
                        mapping
                    });
            }
            mapping
        };
        let mut hg_idmap = BTreeMap::new();
        for (v, csid) in m_clone_data.idmap {
            let hgid = mapping.get(&csid).ok_or_else(|| {
                MononokeError::from(format_err!(
                    "failed to find bonsai '{}' mapping to hg",
                    csid
                ))
            })?;
            hg_idmap.insert(v, *hgid);
        }
        let hg_clone_data = CloneData {
            flat_segments: m_clone_data.flat_segments,
            idmap: hg_idmap,
        };
        Ok(hg_clone_data)
    }

    /// resolve a bookmark name to an Hg Changeset
    pub async fn resolve_bookmark(
        &self,
        bookmark: impl AsRef<str>,
        freshness: Freshness,
    ) -> Result<Option<HgChangesetId>, MononokeError> {
        match self
            .repo
            .resolve_bookmark(&BookmarkKey::new(bookmark)?, freshness)
            .await?
        {
            Some(c) => c.hg_id().await,
            None => Ok(None),
        }
    }

    /// Return (at most 10) HgChangesetIds in the range described by the low and high parameters.
    pub async fn get_hg_in_range(
        &self,
        low: HgChangesetId,
        high: HgChangesetId,
    ) -> Result<Vec<HgChangesetId>, MononokeError> {
        const LIMIT: usize = 10;
        let bonsai_hg_mapping = self.blob_repo().bonsai_hg_mapping();
        bonsai_hg_mapping
            .get_hg_in_range(self.ctx(), low, high, LIMIT)
            .await
            .map_err(|e| e.into())
    }

    /// Convert a list of hg changesets to a list of bonsai changesets.
    async fn convert_changeset_ids(
        &self,
        changesets: Vec<HgChangesetId>,
    ) -> Result<Vec<ChangesetId>, MononokeError> {
        Ok(self
            .blob_repo()
            .get_hg_bonsai_mapping(self.ctx().clone(), changesets.to_vec())
            .await
            .context("error fetching hg bonsai mapping")?
            .iter()
            .map(|(_, bcs_id)| *bcs_id)
            .collect())
    }

    /// Check if all changesets in the list are public.
    /// This may treat commits that "recently" became public as draft.
    pub async fn is_all_public(
        &self,
        changesets: &Vec<HgChangesetId>,
    ) -> Result<bool, MononokeError> {
        let len = changesets.len();
        let public_phases = self
            .blob_repo()
            .phases()
            .get_cached_public(
                self.ctx(),
                self.convert_changeset_ids(changesets.to_vec()).await?,
            )
            .await?;
        Ok(len == public_phases.len())
    }

    pub async fn graph_segments(
        &self,
        common: Vec<HgChangesetId>,
        heads: Vec<HgChangesetId>,
    ) -> Result<Vec<HgChangesetSegment>, MononokeError> {
        let bonsai_common = self.convert_changeset_ids(common).await?;
        let bonsai_heads = self.convert_changeset_ids(heads).await?;

        let segments = self
            .repo()
            .repo()
            .commit_graph()
            .ancestors_difference_segments(self.ctx(), bonsai_heads, bonsai_common)
            .await?;

        let bonsai_hg_mapping = stream::iter(segments.clone())
            .flat_map(|segment| {
                stream::iter([segment.head, segment.base])
                    .chain(stream::iter(segment.parents).map(|parent| parent.cs_id))
            })
            .chunks(100)
            .then(move |chunk| async move {
                let mapping = self
                    .blob_repo()
                    .get_hg_bonsai_mapping(self.ctx().clone(), chunk.to_vec())
                    .await
                    .context("error fetching hg bonsai mapping")?;
                Ok::<_, Error>(mapping)
            })
            .try_collect::<Vec<Vec<(HgChangesetId, ChangesetId)>>>()
            .await?
            .into_iter()
            .flatten()
            .map(|(hgid, csid)| (csid, hgid))
            .collect::<HashMap<_, _>>();

        segments
            .into_iter()
            .map(|segment| {
                Ok(HgChangesetSegment {
                    head: *bonsai_hg_mapping.get(&segment.head).ok_or_else(|| {
                        MononokeError::InvalidRequest(format!(
                            "failed to find hg equivalent for segment head {}",
                            segment.head
                        ))
                    })?,
                    base: *bonsai_hg_mapping.get(&segment.base).ok_or_else(|| {
                        MononokeError::InvalidRequest(format!(
                            "failed to find hg equivalent for segment base {}",
                            segment.base
                        ))
                    })?,
                    length: segment.length,
                    parents: segment
                        .parents
                        .into_iter()
                        .map(|parent| {
                            Ok(HgChangesetSegmentParent {
                                hgid: *bonsai_hg_mapping.get(&parent.cs_id).ok_or_else(|| {
                                    MononokeError::InvalidRequest(format!(
                                        "failed to find hg equivalent for segment parent {}",
                                        parent
                                    ))
                                })?,
                                location: parent
                                    .location
                                    .map(|location| {
                                        Ok::<_, Error>(Location {
                                            descendant: *bonsai_hg_mapping.get(&location.head).ok_or_else(
                                                || {
                                                    MononokeError::InvalidRequest(format!(
                                                        "failed to find hg equivalent for location head {}",
                                                        location.head
                                                    ))
                                                },
                                            )?,
                                            distance: location.distance,
                                        })
                                    })
                                    .transpose()?,
                            })
                        })
                        .collect::<Result<_, MononokeError>>()?,
                })
            })
            .collect::<Result<_, MononokeError>>()
    }

    /// Return a mapping of commits to their parents that are in the segment of
    /// of the commit graph bounded by common and heads.
    ///
    /// This should be used for public fetches only, since for draft commits we need to
    /// make sure filenodes are derived before sending.
    pub async fn get_graph_mapping_stream(
        &self,
        common: Vec<HgChangesetId>,
        heads: Vec<HgChangesetId>,
    ) -> Result<
        impl Stream<Item = Result<(HgChangesetId, Vec<HgChangesetId>), MononokeError>> + 'static,
        MononokeError,
    > {
        let ctx = self.ctx().clone();
        let blob_repo = self.blob_repo().clone();
        let bonsai_common = self.convert_changeset_ids(common).await?;
        let bonsai_heads = self.convert_changeset_ids(heads).await?;
        debug!(ctx.logger(), "Streaming Commit Graph...");
        let commit_graph_stream = self
            .repo()
            .repo()
            .commit_graph()
            .ancestors_difference_stream(&ctx, bonsai_heads, bonsai_common)
            .await?
            .map_err(MononokeError::from)
            .and_then(move |bcs_id| {
                let ctx = ctx.clone();
                let blob_repo = blob_repo.clone();
                async move {
                    blob_repo
                        .get_hg_changeset_and_parents_from_bonsai(ctx, bcs_id)
                        .await
                        .map_err(MononokeError::from)
                }
            });
        Ok(commit_graph_stream)
    }

    /// Return a mapping of commits to their parents that are in the segment of
    /// of the commit graph bounded by common and heads.
    ///
    /// We need to make sure filenodes are derived before sending for draft commits.
    /// This method also return commit's phases.
    pub async fn get_graph_mapping(
        &self,
        common: Vec<HgChangesetId>,
        heads: Vec<HgChangesetId>,
    ) -> Result<Vec<(HgChangesetId, (Vec<HgChangesetId>, bool))>, MononokeError> {
        let ctx = self.ctx().clone();
        let blob_repo = self.blob_repo();
        let phases = blob_repo.phases();

        let common_set: HashSet<_> = common.iter().cloned().collect();
        let heads_vec: Vec<_> = heads.to_vec();

        debug!(ctx.logger(), "Calculating Commit Graph...");
        let (draft_commits, missing_commits) = try_join!(
            find_new_draft_commits_and_derive_filenodes_for_public_roots(
                &ctx,
                blob_repo,
                &common_set,
                &heads_vec,
                phases
            ),
            {
                let bonsai_common = self.convert_changeset_ids(common).await?;
                let bonsai_heads = self.convert_changeset_ids(heads).await?;
                self.repo().repo().commit_graph().ancestors_difference(
                    &ctx,
                    bonsai_heads,
                    bonsai_common,
                )
            }
        )?;

        let cs_parent_mapping = stream::iter(missing_commits.clone())
            .map(move |cs_id| async move {
                let parents = blob_repo
                    .changeset_fetcher()
                    .get_parents(self.ctx(), cs_id)
                    .await?;
                Ok::<_, Error>((cs_id, parents))
            })
            .buffered(100)
            .try_collect::<Vec<_>>()
            .await?;

        let all_cs_ids = cs_parent_mapping
            .clone()
            .into_iter()
            .flat_map(|(_, parents)| parents)
            .chain(missing_commits)
            .collect::<HashSet<_>>();

        let map_chunk_size = 100;

        let bonsai_hg_mapping = stream::iter(all_cs_ids)
            .chunks(map_chunk_size)
            .then(move |chunk| async move {
                let mapping = self
                    .blob_repo()
                    .get_hg_bonsai_mapping(self.ctx().clone(), chunk.to_vec())
                    .await
                    .context("error fetching hg bonsai mapping")?;
                Ok::<_, Error>(mapping)
            })
            .try_collect::<Vec<Vec<(HgChangesetId, ChangesetId)>>>()
            .await?
            .into_iter()
            .flatten()
            .map(|(hgid, csid)| (csid, hgid))
            .collect::<HashMap<_, _>>();

        let get_hg_id_fn = |cs_id| {
            bonsai_hg_mapping
                .get(&cs_id)
                .cloned()
                .with_context(|| format_err!("failed to find bonsai '{}' mapping to hg", cs_id))
        };

        let hg_parent_mapping = cs_parent_mapping
            .into_iter()
            .map(|(cs_id, cs_parents)| {
                let hg_id = get_hg_id_fn(cs_id)?;
                let hg_parents = cs_parents
                    .into_iter()
                    .map(get_hg_id_fn)
                    .collect::<Result<Vec<HgChangesetId>, Error>>()
                    .map_err(MononokeError::from)?;
                let is_draft = draft_commits.contains(&hg_id);
                Ok((hg_id, (hg_parents, is_draft)))
            })
            .collect::<Result<Vec<_>, MononokeError>>()?;

        Ok(hg_parent_mapping)
    }
}

#[cfg(test)]
mod tests {
    use std::collections::BTreeSet;
    use std::sync::Arc;

    use anyhow::Error;
    use blobstore::Loadable;
    use fbinit::FacebookInit;
    use mononoke_api::repo::Repo;
    use mononoke_types::ChangesetId;
    use tests_utils::CreateCommitContext;

    use super::*;
    use crate::RepoContextHgExt;

    #[fbinit::test]
    async fn test_new_hg_context(fb: FacebookInit) -> Result<(), MononokeError> {
        let ctx = CoreContext::test_mock(fb);

        let repo: Repo = test_repo_factory::build_empty(ctx.fb).await?;
        let repo_ctx = RepoContext::new_test(ctx, Arc::new(repo)).await?;

        let hg = repo_ctx.hg();
        assert_eq!(hg.repo().name(), "repo");

        Ok(())
    }

    #[fbinit::test]
    async fn test_trees_under_path(fb: FacebookInit) -> Result<(), MononokeError> {
        let ctx = CoreContext::test_mock(fb);
        let repo: Repo = test_repo_factory::build_empty(ctx.fb).await?;

        // Create test stack; child commit modifies 2 directories.
        let commit_1 = CreateCommitContext::new_root(&ctx, repo.blob_repo())
            .add_file("dir1/a", "1")
            .add_file("dir2/b", "1")
            .add_file("dir3/c", "1")
            .commit()
            .await?;
        let commit_2 = CreateCommitContext::new(&ctx, repo.blob_repo(), vec![commit_1])
            .add_file("dir1/a", "2")
            .add_file("dir3/a/b/c", "1")
            .commit()
            .await?;

        let root_mfid_1 = root_manifest_id(ctx.clone(), repo.blob_repo(), commit_1).await?;
        let root_mfid_2 = root_manifest_id(ctx.clone(), repo.blob_repo(), commit_2).await?;

        let repo_ctx = RepoContext::new_test(ctx, Arc::new(repo)).await?;
        let hg = repo_ctx.hg();

        let trees = hg
            .trees_under_path(
                MononokePath::new(None),
                vec![root_mfid_2],
                vec![root_mfid_1],
                Some(2),
            )
            .try_collect::<Vec<_>>()
            .await?;

        let paths = trees
            .into_iter()
            .map(|(_, path)| format!("{}", path))
            .collect::<BTreeSet<_>>();
        let expected = vec!["", "dir3", "dir1", "dir3/a"]
            .into_iter()
            .map(ToString::to_string)
            .collect::<BTreeSet<_>>();

        assert_eq!(paths, expected);

        Ok(())
    }

    /// Get the HgManifestId of the root tree manifest for the given commit.
    async fn root_manifest_id(
        ctx: CoreContext,
        blob_repo: &BlobRepo,
        csid: ChangesetId,
    ) -> Result<HgManifestId, Error> {
        let hg_cs_id = blob_repo.derive_hg_changeset(&ctx, csid).await?;
        let hg_cs = hg_cs_id
            .load(&ctx, &blob_repo.repo_blobstore().clone())
            .await?;
        Ok(hg_cs.manifestid())
    }
}


/*
 * 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 async_trait::async_trait;
use bytes::Bytes;
use manifest::Entry;
use manifest::Manifest;
use mercurial_types::fetch_manifest_envelope;
use mercurial_types::fetch_manifest_envelope_opt;
use mercurial_types::HgBlobEnvelope;
use mercurial_types::HgFileNodeId;
use mercurial_types::HgManifestEnvelope;
use mercurial_types::HgManifestId;
use mercurial_types::HgNodeHash;
use mercurial_types::HgParents;
use mononoke_api::errors::MononokeError;
use mononoke_types::file_change::FileType;
use mononoke_types::path::MPathElement;
use repo_blobstore::RepoBlobstoreRef;
use revisionstore_types::Metadata;

use super::HgDataContext;
use super::HgDataId;
use super::HgRepoContext;

#[derive(Clone)]
pub struct HgTreeContext {
    #[allow(dead_code)]
    repo: HgRepoContext,
    envelope: HgManifestEnvelope,
}

impl HgTreeContext {
    /// Create a new `HgTreeContext`, representing a single tree manifest node.
    ///
    /// The tree node must exist in the repository. To construct an `HgTreeContext`
    /// for a tree node that may not exist, use `new_check_exists`.
    pub async fn new(
        repo: HgRepoContext,
        manifest_id: HgManifestId,
    ) -> Result<Self, MononokeError> {
        let ctx = repo.ctx();
        let blobstore = repo.blob_repo().repo_blobstore();
        let envelope = fetch_manifest_envelope(ctx, blobstore, manifest_id).await?;
        Ok(Self { repo, envelope })
    }

    pub async fn new_check_exists(
        repo: HgRepoContext,
        manifest_id: HgManifestId,
    ) -> Result<Option<Self>, MononokeError> {
        let ctx = repo.ctx();
        let blobstore = repo.blob_repo().repo_blobstore();
        let envelope = fetch_manifest_envelope_opt(ctx, blobstore, manifest_id).await?;
        Ok(envelope.map(move |envelope| Self { repo, envelope }))
    }

    /// Get the content for this tree manifest node in the format expected
    /// by Mercurial's data storage layer.
    pub fn content_bytes(&self) -> Bytes {
        self.envelope.contents().clone()
    }

    pub fn into_blob_manifest(self) -> anyhow::Result<mercurial_types::blobs::HgBlobManifest> {
        mercurial_types::blobs::HgBlobManifest::parse(self.envelope)
    }

    pub fn entries(
        &self,
    ) -> anyhow::Result<
        impl Iterator<Item = (MPathElement, Entry<HgManifestId, (FileType, HgFileNodeId)>)>,
    > {
        Ok(self.clone().into_blob_manifest()?.list())
    }
}

#[async_trait]
impl HgDataContext for HgTreeContext {
    type NodeId = HgManifestId;

    /// Get the manifest node hash (HgManifestId) for this tree.
    ///
    /// This should be same as the HgManifestId specified when this context was created,
    /// but the value returned here comes from the data loaded from Mononoke.
    fn node_id(&self) -> HgManifestId {
        HgManifestId::new(self.envelope.node_id())
    }

    /// Get the parents of this tree node in a strongly-typed manner.
    ///
    /// Useful for implementing anything that needs to traverse the history
    /// of tree nodes, or otherwise needs to use make further queries using
    /// the returned `HgManifestId`s.
    fn parents(&self) -> (Option<HgManifestId>, Option<HgManifestId>) {
        let (p1, p2) = self.envelope.parents();
        (p1.map(HgManifestId::new), p2.map(HgManifestId::new))
    }

    /// Get the parents of this tree node in a format that can be easily
    /// sent to the Mercurial client as part of a serialized response.
    fn hg_parents(&self) -> HgParents {
        self.envelope.get_parents()
    }

    /// The manifest envelope actually contains the underlying tree bytes
    /// inline, so they can be accessed synchronously and infallibly with the
    /// `content_bytes` method. This method just wraps the bytes in a TryFuture
    /// that immediately succeeds. Note that tree blobs don't have associated
    /// metadata so we just return the default value here.
    async fn content(&self) -> Result<(Bytes, Metadata), MononokeError> {
        Ok((self.content_bytes(), Metadata::default()))
    }
}

#[async_trait]
impl HgDataId for HgManifestId {
    type Context = HgTreeContext;

    fn from_node_hash(hash: HgNodeHash) -> Self {
        HgManifestId::new(hash)
    }

    async fn context(self, repo: HgRepoContext) -> Result<Option<HgTreeContext>, MononokeError> {
        HgTreeContext::new_check_exists(repo, self).await
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use std::sync::Arc;

    use blobstore::Loadable;
    use context::CoreContext;
    use fbinit::FacebookInit;
    use fixtures::Linear;
    use fixtures::TestRepoFixture;
    use mercurial_types::NULL_HASH;
    use mononoke_api::repo::Repo;
    use mononoke_api::repo::RepoContext;
    use mononoke_api::specifiers::HgChangesetId;

    use super::*;
    use crate::RepoContextHgExt;

    #[fbinit::test]
    async fn test_hg_tree_context(fb: FacebookInit) -> Result<(), MononokeError> {
        let ctx = CoreContext::test_mock(fb);
        let repo = Arc::new(Linear::get_custom_test_repo::<Repo>(fb).await);
        let rctx = RepoContext::new_test(ctx.clone(), repo.clone()).await?;

        // Get the HgManifestId of the root tree manifest for a commit in this repo.
        // (Commit hash was found by inspecting the source of the `fixtures` crate.)
        let hg_cs_id = HgChangesetId::from_str("2d7d4ba9ce0a6ffd222de7785b249ead9c51c536")?;
        let hg_cs = hg_cs_id
            .load(&ctx, rctx.blob_repo().repo_blobstore())
            .await?;
        let manifest_id = hg_cs.manifestid();

        let hg = rctx.hg();

        let tree = HgTreeContext::new(hg.clone(), manifest_id).await?;
        assert_eq!(manifest_id, tree.node_id());

        let content = tree.content_bytes();

        // The content here is the representation of the format in which
        // the Mercurial client would store a tree manifest node.
        let expected = &b"1\0b8e02f6433738021a065f94175c7cd23db5f05be\nfiles\0b8e02f6433738021a065f94175c7cd23db5f05be\n"[..];
        assert_eq!(content, expected);

        let tree = HgTreeContext::new_check_exists(hg.clone(), manifest_id).await?;
        assert!(tree.is_some());

        let null_id = HgManifestId::new(NULL_HASH);
        let null_tree = HgTreeContext::new(hg.clone(), null_id).await;
        assert!(null_tree.is_err());

        let null_tree = HgTreeContext::new_check_exists(hg.clone(), null_id).await?;
        assert!(null_tree.is_none());

        Ok(())
    }
}


/*
 * 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.
 */

#![feature(arc_unwrap_or_clone)]

use std::path::Path;
use std::sync::Arc;

use anyhow::Result;
use arc_swap::ArcSwap;
use async_trait::async_trait;
use cached_config::ConfigHandle;
use cached_config::ConfigStore;
use cached_config::ConfigUpdateWatcher;
use cloned::cloned;
use futures::future::join_all;
use metaconfig_parser::config::configerator_config_handle;
use metaconfig_parser::config::load_configs_from_raw;
use metaconfig_parser::RepoConfigs;
use metaconfig_parser::StorageConfigs;
use repos::RawRepoConfigs;
use slog::error;
use slog::info;
use slog::trace;
use slog::Logger;
use stats::prelude::*;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;

const LIVENESS_INTERVAL: u64 = 300;
type Swappable<T> = Arc<ArcSwap<T>>;

define_stats! {
    prefix = "mononoke.config_refresh";
    refresh_failure_count: timeseries(Average, Sum, Count),
    liveness_count: timeseries(Average, Sum, Count),
}

/// Configuration provider and update notifier for all of Mononoke services
/// and jobs. The configurations provided by this struct are always up-to-date
/// with its source.
pub struct MononokeConfigs {
    repo_configs: Swappable<RepoConfigs>,
    storage_configs: Swappable<StorageConfigs>,
    update_receivers: Swappable<Vec<Arc<dyn ConfigUpdateReceiver>>>,
    maybe_config_updater: Option<JoinHandle<()>>,
    maybe_liveness_updater: Option<JoinHandle<()>>,
    maybe_config_handle: Option<ConfigHandle<RawRepoConfigs>>,
}

impl MononokeConfigs {
    /// Create a new instance of MononokeConfigs with configurations backed via ConfigStore.
    /// If the config path points to a dynamic config source (e.g. configerator), this enables
    /// auto-refresh of those configurations.
    pub fn new(
        config_path: impl AsRef<Path>,
        config_store: &ConfigStore,
        runtime_handle: Handle,
        logger: Logger,
    ) -> Result<Self> {
        let storage_configs = metaconfig_parser::load_storage_configs(&config_path, config_store)?;
        let storage_configs = Arc::new(ArcSwap::from_pointee(storage_configs));
        let repo_configs = metaconfig_parser::load_repo_configs(&config_path, config_store)?;
        let repo_configs = Arc::new(ArcSwap::from_pointee(repo_configs));
        let update_receivers = Arc::new(ArcSwap::from_pointee(vec![]));
        let maybe_config_handle = configerator_config_handle(config_path.as_ref(), config_store)?;
        let maybe_config_watcher = maybe_config_handle
            .as_ref()
            .map(|config_handle| config_handle.watcher())
            .transpose()?;
        // If we are dynamically updating the config, we need to have a liveness updater process in place.
        let maybe_liveness_updater = maybe_config_watcher
            .as_ref()
            .map(|_| runtime_handle.spawn(liveness_updater()));
        // If the configuration is backed by a static source, the config update watcher
        // and the config updater handle will be None.
        let maybe_config_updater = maybe_config_watcher.map(|config_watcher| {
            cloned!(storage_configs, repo_configs, update_receivers);
            runtime_handle.spawn(watch_and_update(
                repo_configs,
                storage_configs,
                update_receivers,
                config_watcher,
                logger,
            ))
        });
        Ok(Self {
            repo_configs,
            storage_configs,
            update_receivers,
            maybe_config_updater,
            maybe_config_handle,
            maybe_liveness_updater,
        })
    }

    /// The latest repo configs fetched from the underlying configuration store.
    pub fn repo_configs(&self) -> Arc<RepoConfigs> {
        // Load full since there could be lots of calls to repo_configs.
        self.repo_configs.load_full()
    }

    /// The latest storage configs fetched from the underlying configuration store.
    pub fn storage_configs(&self) -> Arc<StorageConfigs> {
        // Load full since there could be lots of calls to storage_configs.
        self.storage_configs.load_full()
    }

    /// Is automatic update of the underlying configuration enabled?
    pub fn auto_update_enabled(&self) -> bool {
        // If the config updater handle is none, configs won't be updated.
        self.maybe_config_updater.is_some()
    }

    // Config watcher that can be used to get notified of the latest
    // changes in the underlying config and to act on it. This is useful
    // if the processing to be performed is long running which is not supported
    // via ConfigUpdateReceivers
    pub fn config_watcher(&self) -> Option<ConfigUpdateWatcher<RawRepoConfigs>> {
        self.maybe_config_handle
            .as_ref()
            .and_then(|config_handle| config_handle.watcher().ok())
    }

    /// Register an instance of ConfigUpdateReceiver to receive notifications of updates to
    /// the underlying configs which can then be used to perform further actions. Note that
    /// the operation performed by the ConfigUpdateReceiver should not be too long running.
    /// If that's the case, use config_watcher method instead.
    pub fn register_for_update(&self, update_receiver: Arc<dyn ConfigUpdateReceiver>) {
        let mut update_receivers = Vec::from_iter(self.update_receivers.load().iter().cloned());
        update_receivers.push(update_receiver);
        self.update_receivers.store(Arc::new(update_receivers));
    }
}

impl Drop for MononokeConfigs {
    // If MononokeConfigs is getting dropped, then we need to terminate the updater
    // process as well.
    fn drop(&mut self) {
        // If the updater process exists, abort it.
        if let Some(updater_handle) = self.maybe_config_updater.as_ref() {
            updater_handle.abort();
        }
        // If the liveness updater process exists, abort it.
        if let Some(liveness_updater) = self.maybe_liveness_updater.as_ref() {
            liveness_updater.abort();
        }
    }
}

async fn liveness_updater() {
    loop {
        STATS::liveness_count.add_value(1);
        tokio::time::sleep(tokio::time::Duration::from_secs(LIVENESS_INTERVAL)).await;
    }
}

async fn watch_and_update(
    repo_configs: Swappable<RepoConfigs>,
    storage_configs: Swappable<StorageConfigs>,
    update_receivers: Swappable<Vec<Arc<dyn ConfigUpdateReceiver>>>,
    mut config_watcher: ConfigUpdateWatcher<RawRepoConfigs>,
    logger: Logger,
) {
    loop {
        match config_watcher.wait_for_next().await {
            Ok(raw_repo_configs) => {
                info!(
                    logger,
                    "Raw Repo Configs changed in config store, applying update"
                );
                trace!(logger, "Applied configs: {:?}", raw_repo_configs);
                match load_configs_from_raw(Arc::unwrap_or_clone(raw_repo_configs)) {
                    Ok((new_repo_configs, new_storage_configs)) => {
                        let new_repo_configs = Arc::new(new_repo_configs);
                        let new_storage_configs = Arc::new(new_storage_configs);
                        repo_configs.store(new_repo_configs.clone());
                        storage_configs.store(new_storage_configs.clone());
                        let receivers = update_receivers.load();
                        let update_tasks = receivers.iter().map(|receiver| {
                            receiver
                                .apply_update(new_repo_configs.clone(), new_storage_configs.clone())
                        });
                        if let Err(e) = join_all(update_tasks)
                            .await
                            .into_iter()
                            .collect::<Result<Vec<_>>>()
                        {
                            error!(
                                logger,
                                "Failure in sending config update to receivers. Error: {:?}", e
                            );
                            STATS::refresh_failure_count.add_value(1);
                        } else {
                            info!(logger, "Successfully applied config update");
                            // Need to publish a value of 0 to keep the counter alive
                            STATS::refresh_failure_count.add_value(0);
                        }
                    }
                    Err(e) => {
                        error!(
                            logger,
                            "Failure in parsing config from raw config. Error: {:?}", e
                        );
                        STATS::refresh_failure_count.add_value(1);
                    }
                }
            }
            Err(e) => {
                error!(
                    logger,
                    "Failure in fetching latest config change. Error: {:?}", e
                );
                STATS::refresh_failure_count.add_value(1);
            }
        }
    }
}

/// Trait defining methods related to config update notification. A struct implementing
/// this trait can be configured to receive the most updated config value everytime the
/// underlying config changes.
#[async_trait]
pub trait ConfigUpdateReceiver: Send + Sync {
    /// Method containing the logic to be executed when the configuration is updated. This
    /// should not be too long running since the config updates will wait for all update
    /// receivers before checking for the next config update.
    async fn apply_update(
        &self,
        repo_configs: Arc<RepoConfigs>,
        storage_configs: Arc<StorageConfigs>,
    ) -> Result<()>;
}


/*
 * 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::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;

use anyhow::format_err;
use anyhow::Error;
use anyhow::Result;
use blobstore::Blobstore;
use blobstore::Loadable;
use bookmarks::BookmarkUpdateLogRef;
use bookmarks::Freshness;
use context::CoreContext;
use futures::stream::TryStreamExt;
use mononoke_types::RawBundle2Id;
use mutable_counters::MutableCountersRef;
use repo_identity::RepoIdentityRef;
use slog::info;
use tempfile::NamedTempFile;
use tokio::fs::File as AsyncFile;
use tokio::fs::OpenOptions;
use tokio::io::AsyncBufReadExt;
use tokio::io::AsyncWriteExt;
use tokio::io::BufReader;
use tokio::time;
use tokio::time::sleep;
use tokio::time::timeout;

pub const LATEST_REPLAYED_REQUEST_KEY: &str = "latest-replayed-request";

pub async fn save_bundle_to_temp_file<B: Blobstore>(
    ctx: &CoreContext,
    blobstore: &B,
    bundle2_id: RawBundle2Id,
) -> Result<NamedTempFile, Error> {
    let tempfile = NamedTempFile::new()?;

    save_bundle_to_file(
        ctx,
        blobstore,
        bundle2_id,
        tempfile.path().to_path_buf(),
        false, /* create */
    )
    .await?;

    Ok(tempfile)
}

pub async fn save_bundle_to_file<B: Blobstore>(
    ctx: &CoreContext,
    blobstore: &B,
    bundle2_id: RawBundle2Id,
    file: PathBuf,
    create: bool,
) -> Result<(), Error> {
    let bytes = bundle2_id.load(ctx, blobstore).await?;
    save_bytes_to_file(bytes.into_bytes(), file, create).await?;

    Ok(())
}

pub async fn save_bytes_to_temp_file<B: AsRef<[u8]>>(bytes: B) -> Result<NamedTempFile, Error> {
    let tempfile = NamedTempFile::new()?;
    save_bytes_to_file(
        bytes,
        tempfile.path().to_path_buf(),
        false, /* create */
    )
    .await?;
    Ok(tempfile)
}

pub async fn save_bytes_to_file<B: AsRef<[u8]>>(
    bytes: B,
    file: PathBuf,
    create: bool,
) -> Result<(), Error> {
    let mut file = OpenOptions::new()
        .create(create)
        .write(true)
        .open(file)
        .await?;

    file.write_all(bytes.as_ref()).await?;
    file.flush().await?;

    Ok(())
}

pub async fn write_to_named_temp_file<B>(bytes: B) -> Result<NamedTempFile, Error>
where
    B: AsRef<[u8]>,
{
    let tempfile = NamedTempFile::new()?;
    let mut file = open_tempfile(&tempfile).await?;

    file.write_all(bytes.as_ref()).await?;
    file.flush().await?;

    Ok(tempfile)
}

async fn open_tempfile(tempfile: &NamedTempFile) -> Result<AsyncFile, Error> {
    let file = OpenOptions::new()
        .write(true)
        .open(tempfile.path().to_path_buf())
        .await?;

    Ok(file)
}

/// Get lines after the first `num` lines in file
pub async fn lines_after(p: impl AsRef<Path>, num: usize) -> Result<Vec<String>, Error> {
    let file = AsyncFile::open(p).await?;
    let reader = BufReader::new(file);
    let mut v: Vec<_> = tokio_stream::wrappers::LinesStream::new(reader.lines())
        .try_collect()
        .await?;
    Ok(v.split_off(num))
}

/// Wait until the file has more than `initial_num` lines, then return new lines
/// Timeout after `timeout_millis` ms.
pub async fn wait_till_more_lines(
    p: impl AsRef<Path>,
    initial_num: usize,
    timeout_millis: u64,
) -> Result<Vec<String>, Error> {
    let p = p.as_ref().to_path_buf();

    let read = async {
        loop {
            let new_lines = lines_after(p.clone(), initial_num).await?;
            let new_num = new_lines.len();
            let stop = new_num > 0;
            if stop {
                return Ok(new_lines);
            }

            sleep(Duration::from_millis(100)).await;
        }
    };

    match timeout(Duration::from_millis(timeout_millis), read).await {
        Ok(Ok(lines)) => Ok(lines),
        Ok(Err(e)) => Err(e),
        Err(_) => Err(Error::msg("timed out waiting for new lines")),
    }
}

pub fn read_file_contents<F: Seek + Read>(f: &mut F) -> Result<String> {
    // NOTE: Normally (for our use case at this time), we don't advance our position in this file,
    // but let's be conservative and seek to the start anyway.
    let pos = SeekFrom::Start(0);
    f.seek(pos)
        .map_err(|e| format_err!("could not seek to {:?}: {:?}", pos, e))?;

    let mut buff = vec![];
    f.read_to_end(&mut buff)
        .map_err(|e| format_err!("could not read: {:?}", e))?;

    String::from_utf8(buff).map_err(|e| format_err!("log file is not valid utf-8: {:?}", e))
}

/// Wait until all of the entries in the queue have been synced to hg
pub async fn wait_for_latest_log_id_to_be_synced(
    ctx: &CoreContext,
    repo: &(impl BookmarkUpdateLogRef + RepoIdentityRef + MutableCountersRef),
    sleep_duration: Duration,
) -> Result<(), Error> {
    wait_for_latest_log_id_for_repo_to_be_synced(ctx, repo, repo, sleep_duration).await
}

pub async fn wait_for_latest_log_id_for_repo_to_be_synced(
    ctx: &CoreContext,
    repo: &impl BookmarkUpdateLogRef,
    target_repo: &(impl RepoIdentityRef + MutableCountersRef),
    sleep_duration: Duration,
) -> Result<(), Error> {
    let largest_id = latest_id_for_repo(ctx, repo).await?;
    wait_for_log_id_for_repo_to_be_synced(ctx, target_repo, largest_id, sleep_duration).await
}

pub async fn latest_id_for_repo(
    ctx: &CoreContext,
    repo: &impl BookmarkUpdateLogRef,
) -> Result<u64, Error> {
    let largest_id = match repo
        .bookmark_update_log()
        .get_largest_log_id(ctx.clone(), Freshness::MostRecent)
        .await?
    {
        Some(id) => id,
        None => return Err(format_err!("Couldn't fetch id from bookmarks update log")),
    };
    Ok(largest_id)
}

pub async fn wait_for_log_id_for_repo_to_be_synced(
    ctx: &CoreContext,
    target_repo: &(impl RepoIdentityRef + MutableCountersRef),
    log_id: u64,
    sleep_duration: Duration,
) -> Result<(), Error> {
    let target_repo_id = target_repo.repo_identity().id();
    /*
        In mutable counters table we store the latest bookmark id replayed by
        mercurial with LATEST_REPLAYED_REQUEST_KEY key. We use this key to
        extract the latest replayed id and compare it with the bookmark log id
        provided as argument If the replayed id is larger or equal to log_id id,
        we can try to move the bookmark to the next batch of commits
    */
    // Force first log
    let mut time_since_log = Duration::from_secs(60);

    loop {
        let mut_counters_value = target_repo
            .mutable_counters()
            .get_counter(ctx, LATEST_REPLAYED_REQUEST_KEY)
            .await?
            .ok_or_else(|| {
                format_err!(
                    "Couldn't fetch the counter value from mutable_counters for repo_id {:?}",
                    target_repo_id
                )
            })?;
        if log_id > mut_counters_value.try_into().unwrap() {
            if time_since_log.as_secs() >= 60 {
                time_since_log = Duration::ZERO;
                info!(
                    ctx.logger(),
                    "Waiting for {} to be replayed to hg, the latest replayed is {}, repo: {}",
                    log_id,
                    mut_counters_value,
                    target_repo.repo_identity().name(),
                );
            }
            time_since_log += sleep_duration;
            time::sleep(sleep_duration).await;
        } else {
            break;
        }
    }

    Ok(())
}


/*
 * 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::collections::HashMap;
use std::sync::Arc;

use anyhow::bail;
use anyhow::Error;
use anyhow::Result;
use async_trait::async_trait;
use blobstore::Loadable;
use bookmarks::BookmarkKey;
use borrowed::borrowed;
use bytes_old::Bytes as BytesOld;
use cloned::cloned;
use commit_graph::CommitGraphRef;
use context::CoreContext;
use futures::compat::Stream01CompatExt;
use futures::future;
use futures::stream;
use futures::Stream;
use futures::StreamExt;
use futures::TryFutureExt;
use futures::TryStreamExt;
use futures_01_ext::StreamExt as _;
use getbundle_response::create_filenodes;
use getbundle_response::create_manifest_entries_stream;
use getbundle_response::get_manifests_and_filenodes;
use getbundle_response::PreparedFilenodeEntry;
use getbundle_response::SessionLfsParams;
use maplit::hashmap;
use mercurial_bundles::capabilities::encode_capabilities;
use mercurial_bundles::capabilities::Capabilities;
use mercurial_bundles::changegroup::CgVersion;
use mercurial_bundles::create_bundle_stream;
use mercurial_bundles::parts;
use mercurial_derivation::DeriveHgChangeset;
use mercurial_revlog::RevlogChangeset;
use mercurial_types::HgBlobNode;
use mercurial_types::HgChangesetId;
use mercurial_types::MPath;
use mononoke_types::datetime::Timestamp;
use mononoke_types::hash::Sha256;
use mononoke_types::ChangesetId;
use repo_blobstore::RepoBlobstoreArc;
use repo_blobstore::RepoBlobstoreRef;
use slog::debug;

use crate::darkstorm_verifier::DarkstormVerifier;
use crate::lfs_verifier::LfsVerifier;
use crate::Repo;

#[async_trait]
pub trait FilterExistingChangesets: Send + Sync {
    async fn filter(
        &self,
        ctx: &CoreContext,
        cs_ids: Vec<(ChangesetId, HgChangesetId)>,
    ) -> Result<Vec<(ChangesetId, HgChangesetId)>>;
}

pub async fn create_bundle<'a>(
    ctx: &'a CoreContext,
    repo: &'a Repo,
    bookmark: &'a BookmarkKey,
    bookmark_change: &'a BookmarkChange,
    hg_server_heads: Vec<ChangesetId>,
    lfs_params: SessionLfsParams,
    filenode_verifier: &'a FilenodeVerifier,
    push_vars: Option<HashMap<String, bytes::Bytes>>,
    filter_changesets: Arc<dyn FilterExistingChangesets>,
) -> Result<(BytesOld, HashMap<HgChangesetId, (ChangesetId, Timestamp)>)> {
    let mut commits_to_push: Vec<_> = find_commits_to_push(
        ctx,
        repo,
        // Always add "from" bookmark, because is must to be on the hg server
        // If it's not then the push will fail anyway
        hg_server_heads
            .into_iter()
            .chain(bookmark_change.get_from().into_iter()),
        bookmark_change.get_to(),
    )
    .await?
    .try_collect()
    .await?;
    commits_to_push.reverse();
    let commits_to_push = filter_changesets.filter(ctx, commits_to_push).await?;

    debug!(
        ctx.logger(),
        "generating a bundle with {} commits",
        commits_to_push.len()
    );
    let bundle = create_bundle_impl(
        ctx,
        repo,
        bookmark,
        bookmark_change,
        commits_to_push
            .clone()
            .into_iter()
            .map(|(_, hg_cs_id)| hg_cs_id)
            .collect(),
        lfs_params,
        filenode_verifier,
        push_vars,
    );
    let timestamps = fetch_timestamps(ctx.clone(), repo.clone(), commits_to_push);
    future::try_join(bundle, timestamps).await
}

#[derive(Clone)]
pub enum BookmarkChange {
    Created(ChangesetId),
    Deleted(ChangesetId),
    Moved { from: ChangesetId, to: ChangesetId },
}

impl BookmarkChange {
    pub fn new(
        from_cs_id: Option<ChangesetId>,
        to_cs_id: Option<ChangesetId>,
    ) -> Result<Self, Error> {
        match (from_cs_id, to_cs_id) {
            (Some(ref from), None) => Ok(BookmarkChange::Deleted(*from)),
            (None, Some(ref to)) => Ok(BookmarkChange::Created(*to)),
            (Some(ref from), Some(ref to)) => Ok(BookmarkChange::Moved {
                from: *from,
                to: *to,
            }),
            (None, None) => bail!("unsupported bookmark move: deletion of non-existent bookmark?",),
        }
    }

    fn get_from(&self) -> Option<ChangesetId> {
        use BookmarkChange::*;

        match self {
            Created(_) => None,
            Deleted(cs_id) => Some(*cs_id),
            Moved { from, .. } => Some(*from),
        }
    }

    async fn get_from_hg(&self, ctx: &CoreContext, repo: &Repo) -> Result<Option<HgChangesetId>> {
        Self::maybe_get_hg(ctx, self.get_from(), repo).await
    }

    fn get_to(&self) -> Option<ChangesetId> {
        use BookmarkChange::*;

        match self {
            Created(cs_id) => Some(*cs_id),
            Deleted(_) => None,
            Moved { to, .. } => Some(*to),
        }
    }

    async fn get_to_hg(&self, ctx: &CoreContext, repo: &Repo) -> Result<Option<HgChangesetId>> {
        Self::maybe_get_hg(ctx, self.get_to(), repo).await
    }

    async fn maybe_get_hg(
        ctx: &CoreContext,
        maybe_cs: Option<ChangesetId>,
        repo: &Repo,
    ) -> Result<Option<HgChangesetId>> {
        Ok(match maybe_cs {
            Some(cs_id) => Some(repo.derive_hg_changeset(ctx, cs_id).await?),
            None => None,
        })
    }
}

#[derive(Clone)]
pub enum FilenodeVerifier {
    NoopVerifier,
    LfsVerifier(LfsVerifier),
    DarkstormVerifier(DarkstormVerifier),
}

impl FilenodeVerifier {
    async fn verify_entries<'a>(
        &'a self,
        ctx: &'a CoreContext,
        filenode_entries: &'a HashMap<MPath, Vec<PreparedFilenodeEntry>>,
    ) -> Result<()> {
        let lfs_blobs: Vec<(Sha256, u64)> = filenode_entries
            .values()
            .flat_map(|entries| entries.iter())
            .filter_map(|entry| {
                entry
                    .maybe_get_lfs_pointer()
                    .map(|(sha256, size)| (sha256, size))
            })
            .collect();

        match self {
            Self::NoopVerifier => {}
            Self::LfsVerifier(lfs_verifier) => {
                lfs_verifier.ensure_lfs_presence(ctx, &lfs_blobs).await?;
            }
            // Verification for darkstorm backups - will upload large files bypassing LFS server.
            Self::DarkstormVerifier(ds_verifier) => {
                ds_verifier.upload(ctx, &lfs_blobs).await?;
            }
        }

        Ok(())
    }
}

async fn create_bundle_impl(
    ctx: &CoreContext,
    repo: &Repo,
    bookmark: &BookmarkKey,
    bookmark_change: &BookmarkChange,
    commits_to_push: Vec<HgChangesetId>,
    session_lfs_params: SessionLfsParams,
    filenode_verifier: &FilenodeVerifier,
    push_vars: Option<HashMap<String, bytes::Bytes>>,
) -> Result<BytesOld> {
    let any_commits = !commits_to_push.is_empty();
    let changelog_entries = {
        // These clones are not necessary for futures 0.3 but we need compat for
        // hg methods
        cloned!(ctx);
        let blobstore = repo.repo_blobstore_arc();
        stream::iter(commits_to_push.clone())
            .map(move |hg_cs_id| {
                cloned!(ctx, blobstore);
                async move {
                    let cs = hg_cs_id.load(&ctx, &blobstore).await?;
                    Ok((hg_cs_id, cs))
                }
            })
            .buffered(100)
            .and_then(async move |(hg_cs_id, cs)| {
                let revlogcs = RevlogChangeset::new_from_parts(
                    cs.parents().clone(),
                    cs.manifestid().clone(),
                    cs.user().into(),
                    cs.time().clone(),
                    cs.extra().clone(),
                    cs.files().into(),
                    cs.message().into(),
                );

                let mut v = Vec::new();
                mercurial_revlog::changeset::serialize_cs(&revlogcs, &mut v)?;
                Ok((
                    hg_cs_id.into_nodehash(),
                    HgBlobNode::new(bytes::Bytes::from(v), revlogcs.p1(), revlogcs.p2()),
                ))
            })
    };

    let entries = get_manifests_and_filenodes(ctx, repo, commits_to_push, &session_lfs_params);

    let ((manifests, prepared_filenode_entries), maybe_from, maybe_to) = future::try_join3(
        entries,
        bookmark_change.get_from_hg(ctx, repo),
        bookmark_change.get_to_hg(ctx, repo),
    )
    .await?;

    let mut bundle2_parts = vec![parts::replycaps_part(create_capabilities())?];

    match push_vars {
        Some(push_vars) if !push_vars.is_empty() => {
            bundle2_parts.push(parts::pushvars_part(push_vars)?)
        }
        _ => {}
    }

    debug!(
        ctx.logger(),
        "prepared {} manifests and {} filenodes",
        manifests.len(),
        prepared_filenode_entries.len()
    );
    let cg_version = if session_lfs_params.threshold.is_some() {
        CgVersion::Cg3Version
    } else {
        CgVersion::Cg2Version
    };

    // Check that the filenodes pass the verifier prior to serializing them.

    let filenode_entries = stream::once({
        cloned!(ctx, repo, filenode_verifier);
        async move {
            filenode_verifier
                .verify_entries(&ctx, &prepared_filenode_entries)
                .await?;
            anyhow::Ok(
                create_filenodes(ctx.clone(), repo.clone(), prepared_filenode_entries).compat(),
            )
        }
    })
    .try_flatten()
    .boxed()
    .compat();

    if any_commits {
        bundle2_parts.push(parts::changegroup_part(
            changelog_entries.boxed().compat(),
            Some(filenode_entries.boxify()),
            cg_version,
        )?);

        bundle2_parts.push(parts::treepack_part(
            create_manifest_entries_stream(ctx.clone(), repo.repo_blobstore().clone(), manifests),
            parts::StoreInHgCache::Yes,
        )?);
    }

    bundle2_parts.push(parts::bookmark_pushkey_part(
        bookmark.to_string(),
        maybe_from.map(|x| x.to_string()).unwrap_or_default(),
        maybe_to.map(|x| x.to_string()).unwrap_or_default(),
    )?);

    let compression = None;
    create_bundle_stream(bundle2_parts, compression)
        .compat()
        .try_concat()
        .await
}

async fn fetch_timestamps(
    ctx: CoreContext,
    repo: Repo,
    hg_cs_ids: impl IntoIterator<Item = (ChangesetId, HgChangesetId)>,
) -> Result<HashMap<HgChangesetId, (ChangesetId, Timestamp)>, Error> {
    async move {
        borrowed!(ctx, repo);
        stream::iter(hg_cs_ids.into_iter().map(Result::<_, Error>::Ok))
            .map(move |res| async move {
                let (cs_id, hg_cs_id) = res?;
                hg_cs_id
                    .load(ctx, repo.repo_blobstore())
                    .err_into()
                    .map_ok(move |hg_blob_cs| (hg_cs_id, (cs_id, hg_blob_cs.time().clone().into())))
                    .await
            })
            .buffered(100)
            .try_collect()
            .await
    }
    .await
}

async fn find_commits_to_push<'a>(
    ctx: &'a CoreContext,
    repo: &'a Repo,
    hg_server_heads: impl IntoIterator<Item = ChangesetId>,
    maybe_to_cs_id: Option<ChangesetId>,
) -> Result<impl Stream<Item = Result<(ChangesetId, HgChangesetId)>> + 'a> {
    Ok(repo
        .commit_graph()
        .ancestors_difference_stream(
            ctx,
            maybe_to_cs_id.into_iter().collect(),
            hg_server_heads.into_iter().collect(),
        )
        .await?
        .map_ok(async move |bcs_id| {
            let hg_cs_id = repo.derive_hg_changeset(ctx, bcs_id).await?;
            Ok((bcs_id, hg_cs_id))
        })
        .try_buffered(100))
}

// TODO(stash): this should generate different capabilities depending on whether client
// supports changegroup3 or not
fn create_capabilities() -> BytesOld {
    // List of capabilities that was copied from real bundle generated by Mercurial client.
    let caps_ref = hashmap! {
        "HG20" => vec![],
        "b2x:infinitepush" => vec![],
        "b2x:infinitepushmutation" => vec![],
        "b2x:infinitepushscratchbookmarks" => vec![],
        "b2x:rebase" => vec![],
        "bookmarks" => vec![],
        "changegroup" => vec!["01", "02"],
        "digests" => vec!["md5", "sha1", "sha512"],
        "error" => vec!["abort", "unsupportedcntent", "pushraced", "pushkey"],
        "hgtagsfnodes" => vec![],
        "listkeys" => vec![],
        "phases" => vec!["heads"],
        "pushback" => vec![],
        "pushkey" => vec![],
        "remote-changegroup" => vec!["http", "https"],
        "remotefilelog" => vec!["True"],
        "treemanifest" => vec!["True"],
        "treeonly" => vec!["True"],
    };

    let mut caps = hashmap! {};
    for (key, values) in caps_ref {
        let values = values.into_iter().map(|v| v.to_string()).collect();
        caps.insert(key.to_string(), values);
    }

    encode_capabilities(Capabilities::new(caps))
}