mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
revisionstore: Add EdenApiHistoryStore
Summary: Add an EdenAPI-backed history store. Notably, thanks to the strongly-typed remote store design from the previous diff, it is not possible to construct an `EdenApiHistoryStore` for trees, even when the underlying remote store is behind a trait object. (This is because EdenAPI does not support fetching history for trees.) Reviewed By: quark-zju Differential Revision: D22492162 fbshipit-source-id: 23f1393919c4e8ac0918d2009a16f482d90df15c
This commit is contained in:
parent
670ed17ba6
commit
9a536eb3b0
@ -122,7 +122,7 @@ mod tests {
|
||||
let files = hashmap! { k.clone() => d.data.clone() };
|
||||
let trees = HashMap::new();
|
||||
|
||||
let client = FakeEdenApi::new(files, trees);
|
||||
let client = FakeEdenApi::new().files(files).trees(trees).into_arc();
|
||||
let remote_files = EdenApiRemoteStore::<File>::new("repo".parse()?, client.clone());
|
||||
let remote_trees = EdenApiRemoteStore::<Tree>::new("repo".parse()?, client.clone());
|
||||
|
||||
@ -160,7 +160,7 @@ mod tests {
|
||||
let files = HashMap::new();
|
||||
let trees = hashmap! { k.clone() => d.data.clone() };
|
||||
|
||||
let client = FakeEdenApi::new(files, trees);
|
||||
let client = FakeEdenApi::new().files(files).trees(trees).into_arc();
|
||||
let remote_files = EdenApiRemoteStore::<File>::new("repo".parse()?, client.clone());
|
||||
let remote_trees = EdenApiRemoteStore::<Tree>::new("repo".parse()?, client.clone());
|
||||
|
||||
@ -193,7 +193,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_missing() -> Result<()> {
|
||||
// Set up empty EdenApi remote store.
|
||||
let client = FakeEdenApi::new(HashMap::new(), HashMap::new());
|
||||
let client = FakeEdenApi::new().into_arc();
|
||||
let remote = EdenApiRemoteStore::<File>::new("repo".parse()?, client);
|
||||
|
||||
// Set up local mutable store.
|
||||
|
153
eden/scm/lib/revisionstore/src/edenapi/history.rs
Normal file
153
eden/scm/lib/revisionstore/src/edenapi/history.rs
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This software may be used and distributed according to the terms of the
|
||||
* GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::prelude::*;
|
||||
use parking_lot::Mutex;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use types::{Key, NodeInfo};
|
||||
|
||||
use crate::{
|
||||
historystore::{HgIdHistoryStore, HgIdMutableHistoryStore, RemoteHistoryStore},
|
||||
localstore::LocalStore,
|
||||
types::StoreKey,
|
||||
};
|
||||
|
||||
use super::{hgid_keys, EdenApiRemoteStore, File};
|
||||
|
||||
/// A history store backed by an `EdenApiRemoteStore` and a mutable store.
|
||||
///
|
||||
/// This type can only be created from an `EdenApiRemoteStore<File>`; attempting
|
||||
/// to create one from a remote store for trees will panic since EdenAPI does
|
||||
/// not support fetching tree history.
|
||||
///
|
||||
/// Data will be fetched over the network via the remote store and stored in the
|
||||
/// mutable store before being returned to the caller. This type is not exported
|
||||
/// because it is intended to be used as a trait object.
|
||||
pub(super) struct EdenApiHistoryStore {
|
||||
remote: Arc<EdenApiRemoteStore<File>>,
|
||||
store: Arc<dyn HgIdMutableHistoryStore>,
|
||||
runtime: Mutex<Runtime>,
|
||||
}
|
||||
|
||||
impl EdenApiHistoryStore {
|
||||
pub(super) fn new(
|
||||
remote: Arc<EdenApiRemoteStore<File>>,
|
||||
store: Arc<dyn HgIdMutableHistoryStore>,
|
||||
) -> Self {
|
||||
// XXX: The interface of the `HgIdRemoteStore` trait does not allow
|
||||
// construction of the underlying stores to fail, so if the runtime
|
||||
// does fail to start all we can do is panic.
|
||||
let runtime = Mutex::new(Runtime::new().expect("Failed to start Tokio runtime"));
|
||||
Self {
|
||||
remote,
|
||||
store,
|
||||
runtime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteHistoryStore for EdenApiHistoryStore {
|
||||
fn prefetch(&self, keys: &[StoreKey]) -> Result<()> {
|
||||
let client = self.remote.client.clone();
|
||||
let repo = self.remote.repo.clone();
|
||||
let keys = hgid_keys(keys);
|
||||
|
||||
let fetch = async move {
|
||||
let mut response = client.history(repo, keys, None, None).await?;
|
||||
while let Some(entry) = response.entries.try_next().await? {
|
||||
self.store.add_entry(&entry)?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let mut rt = self.runtime.lock();
|
||||
rt.block_on(fetch)
|
||||
}
|
||||
}
|
||||
|
||||
impl HgIdHistoryStore for EdenApiHistoryStore {
|
||||
fn get_node_info(&self, key: &Key) -> Result<Option<NodeInfo>> {
|
||||
match self.prefetch(&[StoreKey::hgid(key.clone())]) {
|
||||
Ok(()) => self.store.get_node_info(key),
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalStore for EdenApiHistoryStore {
|
||||
fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
|
||||
self.store.get_missing(keys)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use maplit::hashmap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use types::testutil::*;
|
||||
|
||||
use crate::{
|
||||
edenapi::{File, Tree},
|
||||
indexedloghistorystore::IndexedLogHgIdHistoryStore,
|
||||
remotestore::HgIdRemoteStore,
|
||||
testutil::*,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_file_history() -> Result<()> {
|
||||
// Set up mocked EdenAPI store.
|
||||
let k = key("a", "1");
|
||||
let n = NodeInfo {
|
||||
parents: [key("b", "2"), null_key("a")],
|
||||
linknode: hgid("3"),
|
||||
};
|
||||
let history = hashmap! { k.clone() => n.clone() };
|
||||
|
||||
let client = FakeEdenApi::new().history(history).into_arc();
|
||||
let remote = EdenApiRemoteStore::<File>::new("repo".parse()?, client.clone());
|
||||
|
||||
// Set up local mutable store to write received data.
|
||||
let tmp = TempDir::new()?;
|
||||
let local = Arc::new(IndexedLogHgIdHistoryStore::new(&tmp)?);
|
||||
|
||||
// Set up `EdenApiHistoryStore`.
|
||||
let edenapi = remote.historystore(local.clone());
|
||||
|
||||
// Attempt fetch.
|
||||
let nodeinfo = edenapi.get_node_info(&k)?.expect("history not found");
|
||||
assert_eq!(&nodeinfo, &n);
|
||||
|
||||
// Check that data was written to the local store.
|
||||
let nodeinfo = local.get_node_info(&k)?.expect("history not found");
|
||||
assert_eq!(&nodeinfo, &n);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_tree_history() {
|
||||
let client = FakeEdenApi::new().into_arc();
|
||||
let remote = EdenApiRemoteStore::<Tree>::new("repo".parse().unwrap(), client.clone());
|
||||
|
||||
// Set up local mutable store to write received data.
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let local = Arc::new(IndexedLogHgIdHistoryStore::new(&tmp).unwrap());
|
||||
|
||||
// EdenAPI does not support fetching tree history, so it should
|
||||
// not be possible to get a history store from a tree store.
|
||||
// The following line should panic.
|
||||
let _ = remote.historystore(local);
|
||||
}
|
||||
}
|
@ -23,8 +23,10 @@ use crate::{
|
||||
};
|
||||
|
||||
mod data;
|
||||
mod history;
|
||||
|
||||
use data::EdenApiDataStore;
|
||||
use history::EdenApiHistoryStore;
|
||||
|
||||
/// Convenience aliases for file and tree stores.
|
||||
pub type EdenApiFileStore = EdenApiRemoteStore<File>;
|
||||
@ -74,7 +76,23 @@ impl<T: EdenApiStoreKind> EdenApiRemoteStore<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EdenApiStoreKind> HgIdRemoteStore for EdenApiRemoteStore<T> {
|
||||
impl HgIdRemoteStore for EdenApiRemoteStore<File> {
|
||||
fn datastore(
|
||||
self: Arc<Self>,
|
||||
store: Arc<dyn HgIdMutableDeltaStore>,
|
||||
) -> Arc<dyn RemoteDataStore> {
|
||||
Arc::new(EdenApiDataStore::new(self, store))
|
||||
}
|
||||
|
||||
fn historystore(
|
||||
self: Arc<Self>,
|
||||
store: Arc<dyn HgIdMutableHistoryStore>,
|
||||
) -> Arc<dyn RemoteHistoryStore> {
|
||||
Arc::new(EdenApiHistoryStore::new(self, store))
|
||||
}
|
||||
}
|
||||
|
||||
impl HgIdRemoteStore for EdenApiRemoteStore<Tree> {
|
||||
fn datastore(
|
||||
self: Arc<Self>,
|
||||
store: Arc<dyn HgIdMutableDeltaStore>,
|
||||
@ -86,7 +104,7 @@ impl<T: EdenApiStoreKind> HgIdRemoteStore for EdenApiRemoteStore<T> {
|
||||
self: Arc<Self>,
|
||||
_store: Arc<dyn HgIdMutableHistoryStore>,
|
||||
) -> Arc<dyn RemoteHistoryStore> {
|
||||
unimplemented!()
|
||||
unimplemented!("EdenAPI does not support fetching tree history")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,14 +176,32 @@ impl LocalStore for FakeRemoteHistoryStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FakeEdenApi {
|
||||
files: HashMap<Key, Bytes>,
|
||||
trees: HashMap<Key, Bytes>,
|
||||
history: HashMap<Key, NodeInfo>,
|
||||
}
|
||||
|
||||
impl FakeEdenApi {
|
||||
pub fn new(files: HashMap<Key, Bytes>, trees: HashMap<Key, Bytes>) -> Arc<Self> {
|
||||
Arc::new(Self { files, trees })
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn files(self, files: HashMap<Key, Bytes>) -> Self {
|
||||
Self { files, ..self }
|
||||
}
|
||||
|
||||
pub fn trees(self, trees: HashMap<Key, Bytes>) -> Self {
|
||||
Self { trees, ..self }
|
||||
}
|
||||
|
||||
pub fn history(self, history: HashMap<Key, NodeInfo>) -> Self {
|
||||
Self { history, ..self }
|
||||
}
|
||||
|
||||
pub fn into_arc(self) -> Arc<Self> {
|
||||
Arc::new(self)
|
||||
}
|
||||
|
||||
fn get(map: &HashMap<Key, Bytes>, keys: Vec<Key>) -> Result<Fetch<DataEntry>, EdenApiError> {
|
||||
@ -222,11 +240,23 @@ impl EdenApi for FakeEdenApi {
|
||||
async fn history(
|
||||
&self,
|
||||
_repo: RepoName,
|
||||
_keys: Vec<Key>,
|
||||
keys: Vec<Key>,
|
||||
_length: Option<u32>,
|
||||
_progress: Option<ProgressCallback>,
|
||||
) -> Result<Fetch<HistoryEntry>, EdenApiError> {
|
||||
unimplemented!()
|
||||
let entries = keys
|
||||
.into_iter()
|
||||
.map(|key| {
|
||||
let nodeinfo = self.history.get(&key).context("Not found")?.clone();
|
||||
Ok(HistoryEntry { key, nodeinfo })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Fetch {
|
||||
meta: vec![ResponseMeta::default()],
|
||||
entries: Box::pin(stream::iter(entries)),
|
||||
stats: Box::pin(future::ok(Stats::default())),
|
||||
})
|
||||
}
|
||||
|
||||
async fn trees(
|
||||
|
Loading…
Reference in New Issue
Block a user