mirror of
https://github.com/facebook/sapling.git
synced 2024-10-16 11:52:02 +03:00
e988a88be9
Summary: Context: https://fb.workplace.com/groups/rust.language/permalink/3338940432821215/ This codemod replaces *all* dependencies on `//common/rust/renamed:futures-preview` with `fbsource//third-party/rust:futures-preview` and their uses in Rust code from `futures_preview::` to `futures::`. This does not introduce any collisions with `futures::` meaning 0.1 futures because D20168958 previously renamed all of those to `futures_old::` in crates that depend on *both* 0.1 and 0.3 futures. Codemod performed by: ``` rg \ --files-with-matches \ --type-add buck:TARGETS \ --type buck \ --glob '!/experimental' \ --regexp '(_|\b)rust(_|\b)' \ | sed 's,TARGETS$,:,' \ | xargs \ -x \ buck query "labels(srcs, rdeps(%Ss, //common/rust/renamed:futures-preview, 1))" \ | xargs sed -i 's,\bfutures_preview::,futures::,' rg \ --files-with-matches \ --type-add buck:TARGETS \ --type buck \ --glob '!/experimental' \ --regexp '(_|\b)rust(_|\b)' \ | xargs sed -i 's,//common/rust/renamed:futures-preview,fbsource//third-party/rust:futures-preview,' ``` Reviewed By: k21 Differential Revision: D20213432 fbshipit-source-id: 07ee643d350c5817cda1f43684d55084f8ac68a6
204 lines
6.4 KiB
Rust
204 lines
6.4 KiB
Rust
/*
|
|
* 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::convert::TryInto;
|
|
use std::fmt;
|
|
use std::future::Future;
|
|
use std::pin::Pin;
|
|
|
|
use anyhow::format_err;
|
|
use bytes::{Bytes, BytesMut};
|
|
use cloned::cloned;
|
|
use context::CoreContext;
|
|
use filestore::{self, get_metadata, FetchKey};
|
|
use futures::compat::{Future01CompatExt, Stream01CompatExt};
|
|
use futures::future::{FutureExt, Shared};
|
|
use futures::stream::TryStreamExt;
|
|
|
|
use crate::errors::MononokeError;
|
|
use crate::repo::RepoContext;
|
|
|
|
/// A file's ID is its content id.
|
|
pub use mononoke_types::ContentId as FileId;
|
|
|
|
/// The type of a file.
|
|
pub use mononoke_types::FileType;
|
|
|
|
/// Metadata about a file.
|
|
pub use mononoke_types::ContentMetadata as FileMetadata;
|
|
|
|
#[derive(Clone)]
|
|
pub struct FileContext {
|
|
repo: RepoContext,
|
|
fetch_key: FetchKey,
|
|
metadata: Shared<Pin<Box<dyn Future<Output = Result<FileMetadata, MononokeError>> + Send>>>,
|
|
}
|
|
|
|
impl fmt::Debug for FileContext {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"FileContext(repo={:?} fetch_key={:?})",
|
|
self.repo().name(),
|
|
self.fetch_key
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Context for accessing a file in a repository.
|
|
///
|
|
/// Files are content-addressed, so if the same file occurs in multiple
|
|
/// places in the repository, this context represents all of them. As such,
|
|
/// it's not possible to go back to the commit or path from a `FileContext`.
|
|
///
|
|
/// See `ChangesetPathContext` if you need to refer to a specific file in a
|
|
/// specific commit.
|
|
impl FileContext {
|
|
/// Create a new FileContext. The file must exist in the repository.
|
|
///
|
|
/// To construct a `FileContext` for a file that might not exist, use
|
|
/// `new_check_exists`.
|
|
pub(crate) fn new(repo: RepoContext, fetch_key: FetchKey) -> Self {
|
|
let metadata = {
|
|
cloned!(repo, fetch_key);
|
|
async move {
|
|
get_metadata(repo.blob_repo().blobstore(), repo.ctx().clone(), &fetch_key)
|
|
.compat()
|
|
.await
|
|
.map_err(MononokeError::from)
|
|
.and_then(|metadata| {
|
|
metadata.ok_or_else(|| content_not_found_error(&fetch_key))
|
|
})
|
|
}
|
|
};
|
|
let metadata = metadata.boxed().shared();
|
|
Self {
|
|
repo,
|
|
fetch_key,
|
|
metadata,
|
|
}
|
|
}
|
|
|
|
/// Create a new FileContext using an ID that might not exist. Returns
|
|
/// `None` if the file doesn't exist.
|
|
pub(crate) async fn new_check_exists(
|
|
repo: RepoContext,
|
|
fetch_key: FetchKey,
|
|
) -> Result<Option<Self>, MononokeError> {
|
|
// Try to get the file metadata immediately to see if it exists.
|
|
let file = get_metadata(repo.blob_repo().blobstore(), repo.ctx().clone(), &fetch_key)
|
|
.compat()
|
|
.await?
|
|
.map(|metadata| {
|
|
let metadata = async move { Ok(metadata) };
|
|
let metadata = metadata.boxed().shared();
|
|
Self {
|
|
repo,
|
|
fetch_key,
|
|
metadata,
|
|
}
|
|
});
|
|
Ok(file)
|
|
}
|
|
|
|
/// The context for this query.
|
|
pub(crate) fn ctx(&self) -> &CoreContext {
|
|
&self.repo.ctx()
|
|
}
|
|
|
|
/// The `RepoContext` for this query.
|
|
pub(crate) fn repo(&self) -> &RepoContext {
|
|
&self.repo
|
|
}
|
|
|
|
/// Return the ID of the file.
|
|
pub async fn id(&self) -> Result<FileId, MononokeError> {
|
|
let meta = self.metadata().await?;
|
|
Ok(meta.content_id)
|
|
}
|
|
|
|
/// Return the metadata for a file.
|
|
pub async fn metadata(&self) -> Result<FileMetadata, MononokeError> {
|
|
self.metadata.clone().await
|
|
}
|
|
|
|
/// Return the content for the file.
|
|
///
|
|
/// This method buffers the full file content in memory, which may
|
|
/// be expensive in the case of large files.
|
|
pub async fn content_concat(&self) -> Result<Bytes, MononokeError> {
|
|
let bytes = filestore::fetch_concat_opt(
|
|
self.repo().blob_repo().blobstore(),
|
|
self.ctx().clone(),
|
|
&self.fetch_key,
|
|
)
|
|
.compat()
|
|
.await;
|
|
|
|
match bytes {
|
|
Ok(Some(bytes)) => Ok(bytes),
|
|
Ok(None) => Err(content_not_found_error(&self.fetch_key)),
|
|
Err(e) => Err(MononokeError::from(e)),
|
|
}
|
|
}
|
|
|
|
/// Return the content for a range within the file.
|
|
///
|
|
/// If the range goes past the end of the file, then content up to
|
|
/// the end of the file is returned. If the range starts past the
|
|
/// end of the file, then an empty buffer is returned.
|
|
pub async fn content_range_concat(
|
|
&self,
|
|
start: u64,
|
|
size: u64,
|
|
) -> Result<Bytes, MononokeError> {
|
|
let ret = filestore::fetch_range_with_size(
|
|
self.repo().blob_repo().blobstore(),
|
|
self.ctx().clone(),
|
|
&self.fetch_key,
|
|
start,
|
|
size,
|
|
)
|
|
.compat()
|
|
.await;
|
|
|
|
match ret {
|
|
Ok(Some((stream, size))) => {
|
|
let size = size.try_into().map_err(|_| {
|
|
MononokeError::from(format_err!("content too large: {:?}", self.fetch_key))
|
|
})?;
|
|
|
|
let bytes = stream
|
|
.compat()
|
|
.map_err(MononokeError::from)
|
|
.try_fold(
|
|
BytesMut::with_capacity(size),
|
|
|mut buff, chunk| async move {
|
|
buff.extend_from_slice(&chunk);
|
|
Ok(buff)
|
|
},
|
|
)
|
|
.await?
|
|
.freeze();
|
|
|
|
Ok(bytes)
|
|
}
|
|
Ok(None) => Err(content_not_found_error(&self.fetch_key)),
|
|
Err(e) => Err(MononokeError::from(e)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// File contexts should only exist for files that are known to be in the
|
|
/// blobstore. If attempting to access the content results in an error, this
|
|
/// error is returned. This is an internal error, as it means either the data
|
|
/// has been lost from the blobstore, or the file context was erroneously
|
|
/// constructed.
|
|
fn content_not_found_error(fetch_key: &FetchKey) -> MononokeError {
|
|
MononokeError::from(format_err!("content not found: {:?}", fetch_key))
|
|
}
|