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:
Arun Kulshreshtha 2019-02-26 09:56:58 -08:00 committed by Facebook Github Bot
parent 472d5cc11a
commit 557f37d144
5 changed files with 82 additions and 9 deletions

View File

@ -35,6 +35,10 @@ pub enum MononokeRepoQuery {
GetHgFile {
filenode: String,
},
GetFileHistory {
filenode: String,
path: String,
},
ListDirectory {
path: String,
revision: Revision,

View File

@ -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),

View File

@ -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),

View File

@ -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())))
}

View File

@ -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),