From 557f37d144c8bef5a89990b6287ee0835488b9b5 Mon Sep 17 00:00:00 2001 From: Arun Kulshreshtha Date: Tue, 26 Feb 2019 09:56:58 -0800 Subject: [PATCH] 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 --- apiserver/src/actor/query.rs | 4 ++++ apiserver/src/actor/repo.rs | 30 ++++++++++++++++++++++++++++-- apiserver/src/actor/response.rs | 23 +++++++++++++++++------ apiserver/src/from_string.rs | 6 +++++- apiserver/src/main.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/apiserver/src/actor/query.rs b/apiserver/src/actor/query.rs index d2e16b1ff8..43ac26450e 100644 --- a/apiserver/src/actor/query.rs +++ b/apiserver/src/actor/query.rs @@ -35,6 +35,10 @@ pub enum MononokeRepoQuery { GetHgFile { filenode: String, }, + GetFileHistory { + filenode: String, + path: String, + }, ListDirectory { path: String, revision: Revision, diff --git a/apiserver/src/actor/repo.rs b/apiserver/src/actor/repo.rs index 3abebe4586..8c8c03fd7b 100644 --- a/apiserver/src/actor/repo.rs +++ b/apiserver/src/actor/repo.rs @@ -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 { + 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), diff --git a/apiserver/src/actor/response.rs b/apiserver/src/actor/response.rs index 4d37723f7f..93361ee959 100644 --- a/apiserver/src/actor/response.rs +++ b/apiserver/src/actor/response.rs @@ -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 + 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::>()).respond_to(req), GetTree { files } => Json(files).respond_to(req), GetChangeset { changeset } => Json(changeset).respond_to(req), diff --git a/apiserver/src/from_string.rs b/apiserver/src/from_string.rs index ef51d45858..b4d2fc36bd 100644 --- a/apiserver/src/from_string.rs +++ b/apiserver/src/from_string.rs @@ -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::from_str(hash).map_err(|e| ErrorKind::InvalidInput(hash.to_string(), Some(e))) } +pub fn get_filenode_id(hash: &str) -> Result { + Ok(HgFileNodeId::new(get_nodehash(hash)?)) +} + pub fn get_sha256_oid(oid: String) -> Result { Sha256::from_str(&oid).map_err(|e| ErrorKind::InvalidInput(oid.to_string(), Some(e.into()))) } diff --git a/apiserver/src/main.rs b/apiserver/src/main.rs index 6d3f6c4b66..2c5dcb0020 100644 --- a/apiserver/src/main.rs +++ b/apiserver/src/main.rs @@ -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, + actix_web::Path, + ), +) -> impl Future { + 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),