edenapi_server: add bookmark endpoint

Summary: Add the EdenAPI endpoint for resolving bookmarks. This is a first pass that just takes a bookmark name as a path variable, to make sure that this is on the right track. We'll want to add a proper request type that includes a list of bookmarks and a response type that can indicate that no bookmark was found. Then the hg bookmark command will also need support for prefix listing capabilities.

Reviewed By: kulshrax

Differential Revision: D26920845

fbshipit-source-id: 067db6a636a75531ee5953392b734c038a58efb6
This commit is contained in:
Carolyn Busch 2021-03-12 12:05:48 -08:00 committed by Facebook GitHub Bot
parent 3525a5b1e3
commit bd89a4c855
7 changed files with 85 additions and 1 deletions

View File

@ -8,6 +8,7 @@ license = "GPLv2+"
[dependencies]
anyhow = "1.0"
async-trait = "0.1.45"
bookmarks = { version = "0.1.0", path = "../bookmarks" }
bytes = { version = "0.5", features = ["serde"] }
cloned = { version = "0.1.0", git = "https://github.com/facebookexperimental/rust-shed.git", branch = "master" }
context = { version = "0.1.0", path = "../server/context" }

View File

@ -0,0 +1,57 @@
/*
* 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 anyhow::Context;
use bookmarks::Freshness;
use bytes::Bytes;
use gotham::state::{FromState, State};
use gotham_derive::{StateData, StaticResponseExtender};
use gotham_ext::{error::HttpError, response::BytesBody};
use mercurial_types::HgChangesetId;
use serde::{Deserialize, Serialize};
use crate::context::ServerContext;
use crate::errors::ErrorKind;
use crate::errors::MononokeErrorExt;
use crate::middleware::RequestContext;
use crate::utils::get_repo;
use super::{EdenApiMethod, HandlerInfo};
/// TODO: add Edenapi and Edenapi::wire type for request and response type
/// add support for prefix listing
#[derive(Clone, Serialize, Debug)]
struct BookmarksResponse {
bookmark_value: Option<HgChangesetId>,
}
#[derive(Debug, Deserialize, StateData, StaticResponseExtender)]
pub struct BookmarksParams {
repo: String,
bookmark: String,
}
pub async fn bookmarks(state: &mut State) -> Result<BytesBody<Bytes>, HttpError> {
let params = BookmarksParams::take_from(state);
state.put(HandlerInfo::new(&params.repo, EdenApiMethod::Bookmarks));
let sctx = ServerContext::borrow_from(state);
let rctx = RequestContext::borrow_from(state).clone();
let repo = get_repo(&sctx, &rctx, &params.repo, None).await?;
let bookmark_value = repo
.resolve_bookmark(params.bookmark, Freshness::MaybeStale)
.await
.map_err(|e| e.into_http_error("error resolving bookmark"))?;
// TODO: add cbor serialization when the response type is changed to an
// Edenapi wire type.
let bytes: Bytes = serde_json::to_string(&BookmarksResponse { bookmark_value })
.context(ErrorKind::SerializationFailed)
.map_err(HttpError::e500)?
.into();
Ok(BytesBody::new(bytes, mime::APPLICATION_JSON))
}

View File

@ -26,6 +26,7 @@ use gotham_ext::response::build_response;
use crate::context::ServerContext;
use crate::middleware::RequestContext;
mod bookmarks;
mod clone;
mod commit;
mod complete_trees;
@ -47,6 +48,7 @@ pub enum EdenApiMethod {
CommitRevlogData,
Clone,
FullIdMapClone,
Bookmarks,
}
impl fmt::Display for EdenApiMethod {
@ -61,6 +63,7 @@ impl fmt::Display for EdenApiMethod {
Self::CommitRevlogData => "commit_revlog_data",
Self::Clone => "clone",
Self::FullIdMapClone => "full_idmap_clone",
Self::Bookmarks => "bookmarks",
};
write!(f, "{}", name)
}
@ -125,6 +128,7 @@ define_handler!(commit_hash_to_location_handler, commit::hash_to_location);
define_handler!(commit_revlog_data_handler, commit::revlog_data);
define_handler!(clone_handler, clone::clone_data);
define_handler!(full_idmap_clone_handler, clone::full_idmap_clone_data);
define_handler!(bookmarks_handler, bookmarks::bookmarks);
fn health_handler(state: State) -> (State, &'static str) {
if ServerContext::borrow_from(&state).will_exit() {
@ -177,5 +181,9 @@ pub fn build_router(ctx: ServerContext) -> Router {
.post("/:repo/full_idmap_clone")
.with_path_extractor::<clone::CloneParams>()
.to(full_idmap_clone_handler);
route
.get("/:repo/bookmarks/:bookmark")
.with_path_extractor::<bookmarks::BookmarksParams>()
.to(bookmarks_handler);
})
}

View File

@ -29,6 +29,7 @@ define_stats! {
commit_revlog_data_duration: dynamic_histogram("{}.commit_revlog_data_ms", (repo: String); 10, 0, 500, Average, Sum, Count; P 5; P 25; P 50; P 75; P 95; P 97; P 99),
clone_duration: dynamic_histogram("{}.clone_data_ms", (repo: String); 10, 0, 500, Average, Sum, Count; P 5; P 25; P 50; P 75; P 95; P 97; P 99),
full_idmap_clone_duration: dynamic_histogram("{}.full_idmap_clone_data_ms", (repo: String); 10, 0, 500, Average, Sum, Count; P 5; P 25; P 50; P 75; P 95; P 97; P 99),
bookmarks_duration: dynamic_histogram("{}.full_idmap_clone_data_ms", (repo: String); 10, 0, 500, Average, Sum, Count; P 5; P 25; P 50; P 75; P 95; P 97; P 99),
}
fn log_stats(state: &mut State, status: StatusCode) -> Option<()> {
@ -66,6 +67,7 @@ fn log_stats(state: &mut State, status: StatusCode) -> Option<()> {
CommitRevlogData => STATS::commit_revlog_data_duration.add_value(dur_ms, (repo,)),
Clone => STATS::clone_duration.add_value(dur_ms, (repo,)),
FullIdMapClone => STATS::full_idmap_clone_duration.add_value(dur_ms, (repo,)),
Bookmarks => STATS::bookmarks_duration.add_value(dur_ms, (repo,)),
}
}

View File

@ -841,7 +841,9 @@ impl RepoContext {
bookmark: impl AsRef<str>,
freshness: BookmarkFreshness,
) -> Result<Option<ChangesetContext>, MononokeError> {
let bookmark = BookmarkName::new(bookmark.as_ref())?;
// a non ascii bookmark name is an invalid request
let bookmark = BookmarkName::new(bookmark.as_ref())
.map_err(|e| MononokeError::InvalidRequest(e.to_string()))?;
let mut cs_id = match freshness {
BookmarkFreshness::MaybeStale => self.warm_bookmarks_cache().get(&bookmark),

View File

@ -11,6 +11,7 @@ async-trait = "0.1.45"
blobrepo = { version = "0.1.0", path = "../blobrepo" }
blobrepo_hg = { version = "0.1.0", path = "../blobrepo/blobrepo_hg" }
blobstore = { version = "0.1.0", path = "../blobstore" }
bookmarks = { version = "0.1.0", path = "../bookmarks" }
bytes = { version = "0.5", features = ["serde"] }
context = { version = "0.1.0", path = "../server/context" }
filestore = { version = "0.1.0", path = "../filestore" }

View File

@ -10,6 +10,7 @@ use std::collections::HashMap;
use anyhow::{self, format_err, Context};
use blobrepo::BlobRepo;
use blobrepo_hg::BlobRepoHg;
use bookmarks::Freshness;
use bytes::Bytes;
use context::CoreContext;
use futures::compat::Stream01CompatExt;
@ -316,6 +317,18 @@ impl HgRepoContext {
};
Ok(hg_clone_data)
}
/// resolve a bookmark name to an Hg Changeset
pub async fn resolve_bookmark(
&self,
bookmark: impl AsRef<str>,
freshness: Freshness,
) -> Result<Option<HgChangesetId>, MononokeError> {
match self.repo.resolve_bookmark(bookmark, freshness).await? {
Some(c) => c.hg_id().await,
None => Ok(None),
}
}
}
async fn hg_convert_idmap_chunk(