mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 00:14:35 +03:00
edenapi: remove cli
Summary: The cli is not used. It is mainly to format requests from JSON and send them. But JSON has its own issues - no binary data support and `hg dbsh`, `hg debugapi` provides debugging too. So let's remove the unused CLI tool. Reviewed By: yancouto Differential Revision: D31465822 fbshipit-source-id: 71574b93a8503643cc503323d6a01f2a87bc41f3
This commit is contained in:
parent
9579fc67be
commit
d9f21f3cab
@ -1,32 +1,23 @@
|
|||||||
# @generated by autocargo from //eden/scm/lib/edenapi:[edenapi,edenapi_cli]
|
# @generated by autocargo from //eden/scm/lib/edenapi:edenapi
|
||||||
[package]
|
[package]
|
||||||
name = "edenapi"
|
name = "edenapi"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "edenapi_cli"
|
|
||||||
path = "src/bin/cli.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-runtime = { path = "../async-runtime" }
|
async-runtime = { path = "../async-runtime" }
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
atty = "0.2"
|
|
||||||
auth = { path = "../auth" }
|
auth = { path = "../auth" }
|
||||||
bytes = { version = "1.0", features = ["serde"] }
|
bytes = { version = "1.0", features = ["serde"] }
|
||||||
chrono = { version = "0.4", features = ["clock", "serde", "std"], default-features = false }
|
chrono = { version = "0.4", features = ["clock", "serde", "std"], default-features = false }
|
||||||
configmodel = { path = "../configmodel" }
|
configmodel = { path = "../configmodel" }
|
||||||
configparser = { path = "../configparser" }
|
|
||||||
dirs = "2.0"
|
|
||||||
edenapi_trait = { path = "trait" }
|
edenapi_trait = { path = "trait" }
|
||||||
edenapi_types = { path = "types" }
|
edenapi_types = { path = "types" }
|
||||||
env_logger = "0.7"
|
|
||||||
futures = { version = "0.3.13", features = ["async-await", "compat"] }
|
futures = { version = "0.3.13", features = ["async-await", "compat"] }
|
||||||
hg-http = { path = "../hg-http" }
|
hg-http = { path = "../hg-http" }
|
||||||
http-client = { path = "../http-client" }
|
http-client = { path = "../http-client" }
|
||||||
itertools = "0.10.1"
|
itertools = "0.10.1"
|
||||||
log = { version = "0.4.8", features = ["kv_unstable"] }
|
|
||||||
metrics = { path = "../metrics" }
|
metrics = { path = "../metrics" }
|
||||||
minibytes = { path = "../minibytes" }
|
minibytes = { path = "../minibytes" }
|
||||||
once_cell = "1.4"
|
once_cell = "1.4"
|
||||||
|
@ -1,291 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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::fmt::Debug;
|
|
||||||
use std::io::stdin;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use env_logger::Env;
|
|
||||||
use futures::prelude::*;
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde_json::Deserializer;
|
|
||||||
use structopt::StructOpt;
|
|
||||||
use tokio::io;
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
|
|
||||||
use configparser::config::{ConfigSet, Options};
|
|
||||||
use edenapi::{Builder, EdenApi, Entries, Response};
|
|
||||||
use edenapi_types::{
|
|
||||||
json::FromJson, wire::ToWire, BookmarkRequest, CommitRevlogDataRequest, FileRequest,
|
|
||||||
HistoryRequest, TreeRequest,
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_CONFIG_FILE: &str = ".hgrc.edenapi";
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
|
||||||
#[structopt(name = "edenapi_cli", about = "Query the EdenAPI server")]
|
|
||||||
enum Command {
|
|
||||||
#[structopt(about = "Check whether server is reachable")]
|
|
||||||
Health(NoRepoArgs),
|
|
||||||
#[structopt(about = "Request files")]
|
|
||||||
Files(Args),
|
|
||||||
#[structopt(about = "Request file history")]
|
|
||||||
History(Args),
|
|
||||||
#[structopt(about = "Request individual tree nodes")]
|
|
||||||
Trees(Args),
|
|
||||||
#[structopt(about = "Request commit revlog data")]
|
|
||||||
CommitRevlogData(Args),
|
|
||||||
#[structopt(about = "Request Bookmarks")]
|
|
||||||
Bookmarks(Args),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
|
||||||
struct NoRepoArgs {
|
|
||||||
#[structopt(long, short, help = "hgrc file to use (default: ~/.hgrc.edenapi)")]
|
|
||||||
config: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
|
||||||
struct Args {
|
|
||||||
repo: String,
|
|
||||||
#[structopt(long, short, help = "hgrc file to use (default: ~/.hgrc.edenapi)")]
|
|
||||||
config: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Setup<R> {
|
|
||||||
repo: String,
|
|
||||||
client: Arc<dyn EdenApi>,
|
|
||||||
requests: Vec<R>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: FromJson + Debug> Setup<R> {
|
|
||||||
/// Common set up for all subcommands.
|
|
||||||
fn from_args(args: Args) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
repo: args.repo,
|
|
||||||
client: init_client(args.config)?,
|
|
||||||
requests: read_requests()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
env_logger::from_env(Env::default().default_filter_or("info")).init();
|
|
||||||
match Command::from_args() {
|
|
||||||
Command::Health(args) => cmd_health(args).await,
|
|
||||||
Command::Files(args) => cmd_files(args).await,
|
|
||||||
Command::History(args) => cmd_history(args).await,
|
|
||||||
Command::Trees(args) => cmd_trees(args).await,
|
|
||||||
Command::CommitRevlogData(args) => cmd_commit_revlog_data(args).await,
|
|
||||||
Command::Bookmarks(args) => cmd_bookmarks(args).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cmd_health(args: NoRepoArgs) -> Result<()> {
|
|
||||||
let client = init_client(args.config)?;
|
|
||||||
let meta = client.health().await?;
|
|
||||||
log::info!("Received response from EdenAPI server:");
|
|
||||||
println!("{:?}", &meta);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cmd_files(args: Args) -> Result<()> {
|
|
||||||
let Setup {
|
|
||||||
repo,
|
|
||||||
client,
|
|
||||||
requests,
|
|
||||||
} = <Setup<FileRequest>>::from_args(args)?;
|
|
||||||
|
|
||||||
for req in requests {
|
|
||||||
log::info!("Requesting content for {} files", req.keys.len(),);
|
|
||||||
|
|
||||||
let response = client.files(repo.clone(), req.keys).await?;
|
|
||||||
handle_response(response).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cmd_bookmarks(args: Args) -> Result<()> {
|
|
||||||
let Setup {
|
|
||||||
repo,
|
|
||||||
client,
|
|
||||||
requests,
|
|
||||||
} = <Setup<BookmarkRequest>>::from_args(args)?;
|
|
||||||
for req in requests {
|
|
||||||
log::info!("Requesting values for {} bookmarks", req.bookmarks.len(),);
|
|
||||||
|
|
||||||
let response = client.bookmarks(repo.clone(), req.bookmarks).await?;
|
|
||||||
handle_vec(response).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cmd_history(args: Args) -> Result<()> {
|
|
||||||
let Setup {
|
|
||||||
repo,
|
|
||||||
client,
|
|
||||||
requests,
|
|
||||||
} = <Setup<HistoryRequest>>::from_args(args)?;
|
|
||||||
|
|
||||||
for req in requests {
|
|
||||||
log::info!("Requesting history for {} files", req.keys.len(),);
|
|
||||||
|
|
||||||
let res = client.history(repo.clone(), req.keys, req.length).await?;
|
|
||||||
handle_response_raw(res).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cmd_trees(args: Args) -> Result<()> {
|
|
||||||
let Setup {
|
|
||||||
repo,
|
|
||||||
client,
|
|
||||||
requests,
|
|
||||||
} = <Setup<TreeRequest>>::from_args(args)?;
|
|
||||||
|
|
||||||
for req in requests {
|
|
||||||
log::info!("Requesting {} tree nodes", req.keys.len());
|
|
||||||
log::trace!("{:?}", &req);
|
|
||||||
|
|
||||||
let res = client.trees(repo.clone(), req.keys, None).await?;
|
|
||||||
handle_response(res).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cmd_commit_revlog_data(args: Args) -> Result<()> {
|
|
||||||
let Setup {
|
|
||||||
repo,
|
|
||||||
client,
|
|
||||||
requests,
|
|
||||||
} = <Setup<CommitRevlogDataRequest>>::from_args(args)?;
|
|
||||||
|
|
||||||
for req in requests {
|
|
||||||
log::info!("Requesting revlog data for {} commits", req.hgids.len());
|
|
||||||
|
|
||||||
let res = client.commit_revlog_data(repo.clone(), req.hgids).await?;
|
|
||||||
handle_response_raw(res).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle the incoming deserialized response by reserializing it
|
|
||||||
/// and dumping it to stdout (only if stdout isn't a TTY, to avoid
|
|
||||||
/// messing up the user's terminal).
|
|
||||||
async fn handle_response<T: ToWire>(res: Response<T>) -> Result<()> {
|
|
||||||
let buf = serialize_and_concat(res.entries).await?;
|
|
||||||
let stats = res.stats.await?;
|
|
||||||
log::info!("{}", &stats);
|
|
||||||
|
|
||||||
if atty::is(atty::Stream::Stdout) {
|
|
||||||
log::warn!("Not writing output because stdout is a TTY");
|
|
||||||
} else {
|
|
||||||
log::info!("Writing output to stdout");
|
|
||||||
io::stdout().write_all(&buf).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_vec<T: ToWire>(res: Vec<T>) -> Result<()> {
|
|
||||||
let buf = serialize_and_concat_vec(res).await?;
|
|
||||||
|
|
||||||
if atty::is(atty::Stream::Stdout) {
|
|
||||||
log::warn!("Not writing output because stdout is a TTY");
|
|
||||||
} else {
|
|
||||||
log::info!("Writing output to stdout");
|
|
||||||
io::stdout().write_all(&buf).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(meyer): Remove when all types have wire type
|
|
||||||
async fn handle_response_raw<T: Serialize>(res: Response<T>) -> Result<()> {
|
|
||||||
let buf = serialize_and_concat_raw(res.entries).await?;
|
|
||||||
let stats = res.stats.await?;
|
|
||||||
log::info!("{}", &stats);
|
|
||||||
|
|
||||||
if atty::is(atty::Stream::Stdout) {
|
|
||||||
log::warn!("Not writing output because stdout is a TTY");
|
|
||||||
} else {
|
|
||||||
log::info!("Writing output to stdout");
|
|
||||||
io::stdout().write_all(&buf).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CBOR serialize and concatenate all items in the incoming stream.
|
|
||||||
///
|
|
||||||
/// Normally, this wouldn't be a good idea since the EdenAPI client just
|
|
||||||
/// deserialized the entries, so immediately re-serializing them is wasteful.
|
|
||||||
/// However, in this case we're explicitly trying to exercise the public API
|
|
||||||
/// of the client, including deserialization. In practice, most users will
|
|
||||||
/// never want the raw (CBOR-encoded) entries.
|
|
||||||
async fn serialize_and_concat<T: ToWire>(entries: Entries<T>) -> Result<Vec<u8>> {
|
|
||||||
entries
|
|
||||||
.err_into()
|
|
||||||
.and_then(|entry| async move { Ok(serde_cbor::to_vec(&entry.to_wire())?) })
|
|
||||||
.try_concat()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serialize_and_concat_vec<T: ToWire>(entries: Vec<T>) -> Result<Vec<u8>> {
|
|
||||||
let serialized = entries
|
|
||||||
.into_iter()
|
|
||||||
.map(|entry| serde_cbor::to_vec(&entry.to_wire()))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
Ok(serialized.concat())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove when all types have wire type
|
|
||||||
async fn serialize_and_concat_raw<T: Serialize>(entries: Entries<T>) -> Result<Vec<u8>> {
|
|
||||||
entries
|
|
||||||
.err_into()
|
|
||||||
.and_then(|entry| async move { Ok(serde_cbor::to_vec(&entry)?) })
|
|
||||||
.try_concat()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_client(config_path: Option<PathBuf>) -> Result<Arc<dyn EdenApi>> {
|
|
||||||
let config = load_config(config_path)?;
|
|
||||||
Ok(Builder::from_config(&config)?.build()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_config(path: Option<PathBuf>) -> Result<ConfigSet> {
|
|
||||||
let path = path
|
|
||||||
.or_else(|| Some(dirs::home_dir()?.join(DEFAULT_CONFIG_FILE)))
|
|
||||||
.context("Failed to get config file path")?;
|
|
||||||
|
|
||||||
log::debug!("Loading config from: {:?}", &path);
|
|
||||||
let mut config = ConfigSet::new();
|
|
||||||
let mut errors = config.load_path(path, &Options::new());
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(config)
|
|
||||||
} else {
|
|
||||||
// Just return the last error for simplicity.
|
|
||||||
Err(errors.pop().unwrap().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_requests<R: FromJson>() -> Result<Vec<R>> {
|
|
||||||
log::info!("Reading requests as JSON from stdin...");
|
|
||||||
Deserializer::from_reader(stdin())
|
|
||||||
.into_iter()
|
|
||||||
.map(|json| Ok(R::from_json(&json?)?))
|
|
||||||
.collect()
|
|
||||||
}
|
|
@ -368,8 +368,7 @@ impl HttpClientBuilder {
|
|||||||
|
|
||||||
/// If specified, the client will write a JSON version of every request
|
/// If specified, the client will write a JSON version of every request
|
||||||
/// it sends to the specified directory. This is primarily useful for
|
/// it sends to the specified directory. This is primarily useful for
|
||||||
/// debugging. The JSON requests can be sent with the `edenapi_cli`, or
|
/// debugging.
|
||||||
/// converted to CBOR with the `make_req` tool and sent with `curl`.
|
|
||||||
pub fn log_dir(mut self, dir: impl AsRef<Path>) -> Self {
|
pub fn log_dir(mut self, dir: impl AsRef<Path>) -> Self {
|
||||||
self.log_dir = Some(dir.as_ref().into());
|
self.log_dir = Some(dir.as_ref().into());
|
||||||
self
|
self
|
||||||
|
Loading…
Reference in New Issue
Block a user