mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 01:07:15 +03:00
Add method to get file history
Summary: This diff adds a new `getfilehistory` endpoint to the API server that takes a filenode/path pair and returns a stream of JSON-encoded history entries (corresponding to `types::LooseHistoryEntry`) representing the full history of that filenode. (Note that the particular serialization format can be changed relatively easily in the future; for now I've chosen JSON for ease of debugging.) Reviewed By: StanislavGlebik Differential Revision: D14197233 fbshipit-source-id: 390f2e528f36fa3ec4b402e2fc3def0f2d529432
This commit is contained in:
parent
472d5cc11a
commit
557f37d144
@ -35,6 +35,10 @@ pub enum MononokeRepoQuery {
|
||||
GetHgFile {
|
||||
filenode: String,
|
||||
},
|
||||
GetFileHistory {
|
||||
filenode: String,
|
||||
path: String,
|
||||
},
|
||||
ListDirectory {
|
||||
path: String,
|
||||
revision: Revision,
|
||||
|
@ -17,10 +17,11 @@ use failure::Error;
|
||||
use futures::future::{join_all, ok};
|
||||
use futures::Stream;
|
||||
use futures::{Future, IntoFuture};
|
||||
use futures_ext::{try_boxfuture, BoxFuture, FutureExt};
|
||||
use futures_ext::{try_boxfuture, BoxFuture, FutureExt, StreamExt};
|
||||
use http::uri::Uri;
|
||||
use mercurial_types::manifest::Content;
|
||||
use mononoke_api;
|
||||
use remotefilelog;
|
||||
use scuba_ext::ScubaSampleBuilder;
|
||||
use slog::Logger;
|
||||
use tracing::TraceContext;
|
||||
@ -28,6 +29,7 @@ use uuid::Uuid;
|
||||
|
||||
use mercurial_types::{HgChangesetId, HgFileNodeId, HgManifestId};
|
||||
use metaconfig_types::RepoConfig;
|
||||
use types::LooseHistoryEntry;
|
||||
|
||||
use mononoke_types::{FileContents, RepositoryId};
|
||||
use reachabilityindex::ReachabilityIndex;
|
||||
@ -164,6 +166,27 @@ impl MononokeRepo {
|
||||
.boxify()
|
||||
}
|
||||
|
||||
fn get_file_history(
|
||||
&self,
|
||||
ctx: CoreContext,
|
||||
filenode: String,
|
||||
path: String,
|
||||
) -> BoxFuture<MononokeRepoResponse, ErrorKind> {
|
||||
let filenode = try_boxfuture!(FS::get_filenode_id(&filenode));
|
||||
let path = try_boxfuture!(FS::get_mpath(path));
|
||||
|
||||
let history =
|
||||
remotefilelog::get_file_history(ctx, self.repo.clone(), filenode, path.clone())
|
||||
.and_then(move |entry| {
|
||||
let entry = LooseHistoryEntry::from(entry);
|
||||
Ok(Bytes::from(serde_json::to_vec(&entry)?))
|
||||
})
|
||||
.from_err()
|
||||
.boxify();
|
||||
|
||||
ok(MononokeRepoResponse::GetFileHistory { history }).boxify()
|
||||
}
|
||||
|
||||
fn is_ancestor(
|
||||
&self,
|
||||
ctx: CoreContext,
|
||||
@ -238,7 +261,9 @@ impl MononokeRepo {
|
||||
|
||||
let repo = self.repo.clone();
|
||||
self.get_hgchangesetid_from_revision(ctx.clone(), revision)
|
||||
.and_then(move |changesetid| mononoke_api::get_content_by_path(ctx, repo, changesetid, mpath))
|
||||
.and_then(move |changesetid| {
|
||||
mononoke_api::get_content_by_path(ctx, repo, changesetid, mpath)
|
||||
})
|
||||
.and_then(move |content| match content {
|
||||
Content::Tree(tree) => Ok(tree),
|
||||
_ => Err(ErrorKind::NotADirectory(path.to_string()).into()),
|
||||
@ -371,6 +396,7 @@ impl MononokeRepo {
|
||||
match msg {
|
||||
GetRawFile { revision, path } => self.get_raw_file(ctx, revision, path),
|
||||
GetHgFile { filenode } => self.get_hg_file(ctx, filenode),
|
||||
GetFileHistory { filenode, path } => self.get_file_history(ctx, filenode, path),
|
||||
GetBlobContent { hash } => self.get_blob_content(ctx, hash),
|
||||
ListDirectory { revision, path } => self.list_directory(ctx, revision, path),
|
||||
GetTree { hash } => self.get_tree(ctx, hash),
|
||||
|
@ -4,14 +4,17 @@
|
||||
// This software may be used and distributed according to the terms of the
|
||||
// GNU General Public License version 2 or any later version.
|
||||
|
||||
use actix_web;
|
||||
use actix_web::{Body, HttpRequest, HttpResponse, Json, Responder};
|
||||
use bytes::Bytes;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use actix_web::{self, dev::BodyStream, Body, HttpRequest, HttpResponse, Json, Responder};
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
|
||||
use super::lfs::BatchResponse;
|
||||
use super::model::{Changeset, Entry, EntryWithSizeAndContentHash};
|
||||
|
||||
type SendBodyStream = Box<Stream<Item = Bytes, Error = actix_web::Error> + Send + 'static>;
|
||||
|
||||
pub enum MononokeRepoResponse {
|
||||
GetRawFile {
|
||||
content: Bytes,
|
||||
@ -19,6 +22,9 @@ pub enum MononokeRepoResponse {
|
||||
GetHgFile {
|
||||
content: Bytes,
|
||||
},
|
||||
GetFileHistory {
|
||||
history: SendBodyStream,
|
||||
},
|
||||
GetBlobContent {
|
||||
content: Bytes,
|
||||
},
|
||||
@ -52,6 +58,12 @@ fn binary_response(content: Bytes) -> HttpResponse {
|
||||
.body(Body::Binary(content.into()))
|
||||
}
|
||||
|
||||
fn streaming_response(stream: SendBodyStream) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/octet-stream")
|
||||
.body(Body::Streaming(stream as BodyStream))
|
||||
}
|
||||
|
||||
impl Responder for MononokeRepoResponse {
|
||||
type Item = HttpResponse;
|
||||
type Error = actix_web::Error;
|
||||
@ -60,9 +72,8 @@ impl Responder for MononokeRepoResponse {
|
||||
use self::MononokeRepoResponse::*;
|
||||
|
||||
match self {
|
||||
GetRawFile { content } | GetBlobContent { content } | GetHgFile { content } => {
|
||||
Ok(binary_response(content))
|
||||
}
|
||||
GetRawFile { content } | GetBlobContent { content } | GetHgFile { content } => Ok(binary_response(content)),
|
||||
GetFileHistory { history } => Ok(streaming_response(history)),
|
||||
ListDirectory { files } => Json(files.collect::<Vec<_>>()).respond_to(req),
|
||||
GetTree { files } => Json(files).respond_to(req),
|
||||
GetChangeset { changeset } => Json(changeset).respond_to(req),
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
use std::{convert::TryFrom, str::FromStr};
|
||||
|
||||
use mercurial_types::{HgChangesetId, HgNodeHash};
|
||||
use mercurial_types::{HgChangesetId, HgFileNodeId, HgNodeHash};
|
||||
use mononoke_types::{hash::Sha256, MPath};
|
||||
|
||||
use crate::errors::ErrorKind;
|
||||
@ -25,6 +25,10 @@ pub fn get_nodehash(hash: &str) -> Result<HgNodeHash, ErrorKind> {
|
||||
HgNodeHash::from_str(hash).map_err(|e| ErrorKind::InvalidInput(hash.to_string(), Some(e)))
|
||||
}
|
||||
|
||||
pub fn get_filenode_id(hash: &str) -> Result<HgFileNodeId, ErrorKind> {
|
||||
Ok(HgFileNodeId::new(get_nodehash(hash)?))
|
||||
}
|
||||
|
||||
pub fn get_sha256_oid(oid: String) -> Result<Sha256, ErrorKind> {
|
||||
Sha256::from_str(&oid).map_err(|e| ErrorKind::InvalidInput(oid.to_string(), Some(e.into())))
|
||||
}
|
||||
|
@ -104,6 +104,31 @@ fn get_hg_file(
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct GetFileHistoryParams {
|
||||
repo: String,
|
||||
filenode: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
fn get_file_history(
|
||||
(state, params): (
|
||||
State<HttpServerState>,
|
||||
actix_web::Path<GetFileHistoryParams>,
|
||||
),
|
||||
) -> impl Future<Item = MononokeRepoResponse, Error = ErrorKind> {
|
||||
state.mononoke.send_query(
|
||||
prepare_fake_ctx(&state),
|
||||
MononokeQuery {
|
||||
repo: params.repo.clone(),
|
||||
kind: MononokeRepoQuery::GetFileHistory {
|
||||
filenode: params.filenode.clone(),
|
||||
path: params.path.clone(),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IsAncestorParams {
|
||||
repo: String,
|
||||
@ -522,6 +547,9 @@ fn main() -> Fallible<()> {
|
||||
.resource("/gethgfile/{filenode}", |r| {
|
||||
r.method(http::Method::GET).with_async(get_hg_file)
|
||||
})
|
||||
.resource("/getfilehistory/{filenode}/{path:.*}", |r| {
|
||||
r.method(http::Method::GET).with_async(get_file_history)
|
||||
})
|
||||
.resource(
|
||||
"/is_ancestor/{ancestor}/{descendant}",
|
||||
|r| r.method(http::Method::GET).with_async(is_ancestor),
|
||||
|
Loading…
Reference in New Issue
Block a user