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]
|
||||
name = "edenapi"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "edenapi_cli"
|
||||
path = "src/bin/cli.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
async-runtime = { path = "../async-runtime" }
|
||||
async-trait = "0.1.51"
|
||||
atty = "0.2"
|
||||
auth = { path = "../auth" }
|
||||
bytes = { version = "1.0", features = ["serde"] }
|
||||
chrono = { version = "0.4", features = ["clock", "serde", "std"], default-features = false }
|
||||
configmodel = { path = "../configmodel" }
|
||||
configparser = { path = "../configparser" }
|
||||
dirs = "2.0"
|
||||
edenapi_trait = { path = "trait" }
|
||||
edenapi_types = { path = "types" }
|
||||
env_logger = "0.7"
|
||||
futures = { version = "0.3.13", features = ["async-await", "compat"] }
|
||||
hg-http = { path = "../hg-http" }
|
||||
http-client = { path = "../http-client" }
|
||||
itertools = "0.10.1"
|
||||
log = { version = "0.4.8", features = ["kv_unstable"] }
|
||||
metrics = { path = "../metrics" }
|
||||
minibytes = { path = "../minibytes" }
|
||||
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
|
||||
/// it sends to the specified directory. This is primarily useful for
|
||||
/// debugging. The JSON requests can be sent with the `edenapi_cli`, or
|
||||
/// converted to CBOR with the `make_req` tool and sent with `curl`.
|
||||
/// debugging.
|
||||
pub fn log_dir(mut self, dir: impl AsRef<Path>) -> Self {
|
||||
self.log_dir = Some(dir.as_ref().into());
|
||||
self
|
||||
|
Loading…
Reference in New Issue
Block a user