Back out "Improved internal representation of GetbundleArgs.bundlecaps"

Summary: mononoke: Original commit changeset: 6cb5124c7893

Reviewed By: StanislavGlebik

Differential Revision: D13751021

fbshipit-source-id: b80da7ebbaaca3324078efda15704c185050b35f
This commit is contained in:
Arthur Kushka 2019-01-21 07:40:59 -08:00 committed by Facebook Github Bot
parent 234c33a241
commit aa4a1deb0a
3 changed files with 55 additions and 209 deletions

View File

@ -142,41 +142,8 @@ pub struct GetbundleArgs {
pub heads: Vec<HgNodeHash>,
/// List of space-delimited hex nodes that the client has in common with the server
pub common: Vec<HgNodeHash>,
/**
* Bundlecaps complex object contains parsed bundlecaps entries.
* First layer contains names of the groups as "treeonly" or "bundle2".
* In some cases group can contain some value, like "bundle2=pushkey bookmarks",
* it means that on next level it will contain map of this parameters.
* In case if internal parameters have values, like "bundle2=changegroup=01,02" it will
* be parsed in the last level - HashSet.
*
* Example of data that can be parsed in bundlecaps object
* (url-encoded symbols were replaced for readability purposes):
* {
* "treeonly",
* "bundle2=
* HG20
* bookmarks
* changegroup=01,02
* digests=md5,sha1,sha512
* error=abort,unsupportedcontent,pushraced,pushkey
* hgtagsfnodes
* listkeys
* phases=heads
* pushkey
* remote-changegroup=http,https
* remotefilelog=True
* treemanifest=True
* treeonly=True",
* "remotefilelog",
* "HG20"
* }
*
* To use this structure, you can simply navigate it with default collection api, eg:
* bundlecaps.contains_key("treeonly")
* bundlecaps["bundle2"]["changegroup"].contains("01")
*/
pub bundlecaps: HashMap<String, HashMap<String, HashSet<String>>>,
/// Comma-delimited set of strings defining client bundle capabilities.
pub bundlecaps: HashSet<Vec<u8>>,
/// Comma-delimited list of strings of ``pushkey`` namespaces. For each namespace listed, a bundle2 part will be included with the content of that namespace.
pub listkeys: Vec<Vec<u8>>,
/// phases: Boolean indicating whether phases data is requested
@ -185,8 +152,11 @@ pub struct GetbundleArgs {
impl Debug for GetbundleArgs {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let listkeys: Vec<_> = self
.listkeys
let bcaps: HashSet<_> = self.bundlecaps
.iter()
.map(|s| String::from_utf8_lossy(&s))
.collect();
let listkeys: Vec<_> = self.listkeys
.iter()
.map(|s| String::from_utf8_lossy(&s))
.collect();
@ -197,7 +167,7 @@ impl Debug for GetbundleArgs {
.field("heads", &heads)
.field("common_len", &self.common.len())
.field("common", &common)
.field("bundlecaps", &self.bundlecaps)
.field("bundlecaps", &bcaps)
.field("listkeys", &listkeys)
.field("phases", &self.phases)
.finish()

View File

@ -4,9 +4,8 @@
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::iter;
use std::iter::FromIterator;
use std::str::{self, FromStr};
use bytes::{Bytes, BytesMut};
@ -14,10 +13,10 @@ use nom::{is_alphanumeric, is_digit, Err, ErrorKind, FindSubstring, IResult, Nee
use HgNodeHash;
use {GetbundleArgs, GettreepackArgs, Request, SingleRequest};
use batch;
use errors;
use errors::*;
use {GetbundleArgs, GettreepackArgs, Request, SingleRequest};
const BAD_UTF8_ERR_CODE: u32 = 111;
@ -46,40 +45,26 @@ named!(
/// Return an identifier of the form [a-zA-Z_][a-zA-Z0-9_]*. Returns Incomplete
/// if it manages to reach the end of input, as there may be more identifier coming.
fn ident_alphanum(input: &[u8]) -> IResult<&[u8], &[u8]> {
fn ident(input: &[u8]) -> IResult<&[u8], &[u8]> {
for (idx, item) in input.iter().enumerate() {
match *item as char {
'a'...'z' | 'A'...'Z' | '_' => continue,
'0'...'9' if idx > 0 => continue,
_ => {
if idx > 0 {
_ => if idx > 0 {
return IResult::Done(&input[idx..], &input[0..idx]);
} else {
return IResult::Error(Err::Code(ErrorKind::AlphaNumeric));
}
}
},
}
}
IResult::Incomplete(Needed::Unknown)
}
/// Return an identifier of the form [a-zA-Z0-9_-]*.
named!(
ident,
take_till1!(|ch| match ch as char {
'0'...'9' | 'a'...'z' | 'A'...'Z' | '_' | '-' => false,
_ => true,
})
);
/// As above, but assumes input is complete, so reaching the end of input means
/// the identifier is the entire input.
fn ident_complete<P>(parser: P) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]>
where
P: Fn(&[u8]) -> IResult<&[u8], &[u8]>,
{
move |input| match parser(input) {
IResult::Incomplete(_) => IResult::Done(&b""[..], input),
fn ident_complete(input: &[u8]) -> IResult<&[u8], &[u8]> {
match ident(input) {
IResult::Incomplete(_) => IResult::Done(b"", input),
other => other,
}
}
@ -127,11 +112,7 @@ named!(
named!(
param_kv<HashMap<Vec<u8>, Vec<u8>>>,
do_parse!(
key: ident_alphanum
>> tag!(b" ")
>> len: integer
>> tag!(b"\n")
>> val: take!(len)
key: ident >> tag!(b" ") >> len: integer >> tag!(b"\n") >> val: take!(len)
>> (iter::once((key.to_vec(), val.to_vec())).collect())
)
);
@ -256,8 +237,7 @@ fn notsemi(b: u8) -> bool {
named!(
cmd<(Vec<u8>, Vec<u8>)>,
do_parse!(
cmd: take_until_and_consume1!(" ")
>> args: take_while!(notsemi)
cmd: take_until_and_consume1!(" ") >> args: take_while!(notsemi)
>> ((cmd.to_vec(), args.to_vec()))
)
);
@ -374,13 +354,14 @@ where
}
}
named!(ident_string<&[u8], String>,
map!(ident_complete(ident), |x| String::from_utf8_lossy(x).into_owned())
);
named!(ident_string_alphanum<&[u8], String>,
map!(ident_complete(ident_alphanum), |x| String::from_utf8_lossy(x).into_owned())
);
/// Parse an ident, and map it to `String`.
fn ident_string(inp: &[u8]) -> IResult<&[u8], String> {
match ident_complete(inp) {
IResult::Done(rest, s) => IResult::Done(rest, String::from_utf8_lossy(s).into_owned()),
IResult::Incomplete(n) => IResult::Incomplete(n),
IResult::Error(e) => IResult::Error(e),
}
}
/// Parse utf8 string, assumes that input is complete
fn utf8_string_complete(inp: &[u8]) -> IResult<&[u8], String> {
@ -395,55 +376,8 @@ fn bytes_complete(inp: &[u8]) -> IResult<&[u8], Bytes> {
IResult::Done(b"", res)
}
// Tags encoded in url-encode format which used in bundlecaps args
const SPACE_TAG: &str = "%0A";
const EQUALS_TAG: &str = "%3D";
const COMMA_TAG: &str = "%2C";
// Parses list of bundlecaps values separated with encoded comma: 1,x,y
named!(bundlecaps_values_list<&[u8], HashSet<String>>,
alt_complete!(
map!(separated_list_complete!(tag!(COMMA_TAG), ident_string), HashSet::from_iter) |
map!(ident_string, |id| HashSet::from_iter(vec!(id)))
)
);
// Parses bundlecap param with encoded commas and equal chars: list=1,x,y
named!(bundlecaps_param<&[u8], (String, HashSet<String>)>,
alt_complete!(
separated_pair!(ident_string, tag!(EQUALS_TAG), bundlecaps_values_list) |
map!(ident_string, |id| (id, HashSet::new()))
)
);
// Parses bundlecap params with encoded commas, equal and empty space chars: "block list=1,x,y"
named!(bundlecaps_params<&[u8], HashMap<String, HashSet<String>>>,
do_parse!(
char!('=') >>
data: separated_list_complete!(tag!(SPACE_TAG), bundlecaps_param) >>
(HashMap::from_iter(data))
)
);
// Parses bundlecap argument
named!(
bundlecaps_arg<&[u8], (String, HashMap<String, HashSet<String>>)>,
alt_complete!(
pair!(ident_string, bundlecaps_params) |
map!(ident_string, |x| (x, HashMap::new()))
)
);
// Parses bundlecap arguments list
named!(
bundlecaps_args<&[u8], HashMap<String, HashMap<String, HashSet<String>>>>,
map!(separated_list_complete!(char!(','), bundlecaps_arg), HashMap::from_iter)
);
macro_rules! replace_expr {
($_t:tt $sub:expr) => {
$sub
};
($_t:tt $sub:expr) => {$sub};
}
macro_rules! count_tts {
@ -569,8 +503,8 @@ fn parse_with_params(
| command!("capabilities", Capabilities, parse_params, {})
| call!(parse_command, "debugwireargs", parse_params, 2+1,
|kv| Ok(Debugwireargs {
one: parseval(&kv, "one", ident_complete(ident_alphanum))?.to_vec(),
two: parseval(&kv, "two", ident_complete(ident_alphanum))?.to_vec(),
one: parseval(&kv, "one", ident_complete)?.to_vec(),
two: parseval(&kv, "two", ident_complete)?.to_vec(),
all_args: kv,
}))
| call!(parse_command, "getbundle", parse_params, 0+1,
@ -582,14 +516,14 @@ fn parse_with_params(
// If those params are needed, they should be parsed here.
heads: parseval_default(&kv, "heads", hashlist)?,
common: parseval_default(&kv, "common", hashlist)?,
bundlecaps: parseval_default(&kv, "bundlecaps", bundlecaps_args)?,
bundlecaps: parseval_default(&kv, "bundlecaps", commavalues)?.into_iter().collect(),
listkeys: parseval_default(&kv, "listkeys", commavalues)?,
phases: parseval_default(&kv, "phases", boolean)?,
})))
| command!("heads", Heads, parse_params, {})
| command!("hello", Hello, parse_params, {})
| command!("listkeys", Listkeys, parse_params, {
namespace => ident_string_alphanum,
namespace => ident_string,
})
| command!("lookup", Lookup, parse_params, {
key => utf8_string_complete,
@ -632,32 +566,22 @@ mod test {
#[test]
fn test_ident() {
assert_eq!(ident(b"01 "), IResult::Done(&b" "[..], &b"01"[..]));
assert_eq!(ident(b"foo"), IResult::Done(&b""[..], &b"foo"[..]));
assert_eq!(
ident(b"1234 "),
IResult::Error(Err::Code(ErrorKind::AlphaNumeric))
);
assert_eq!(
ident(b" 1234 "),
IResult::Error(Err::Code(ErrorKind::AlphaNumeric))
);
assert_eq!(ident(b"foo"), IResult::Incomplete(Needed::Unknown));
assert_eq!(ident(b"foo "), IResult::Done(&b" "[..], &b"foo"[..]));
}
#[test]
fn test_ident_alphanum() {
assert_eq!(
ident_alphanum(b"1234 "),
IResult::Error(Err::Code(ErrorKind::AlphaNumeric))
);
assert_eq!(
ident_alphanum(b" 1234 "),
IResult::Error(Err::Code(ErrorKind::AlphaNumeric))
);
assert_eq!(ident_alphanum(b"foo"), IResult::Incomplete(Needed::Unknown));
assert_eq!(
ident_alphanum(b"foo "),
IResult::Done(&b" "[..], &b"foo"[..])
);
}
#[test]
fn test_param_star() {
let p = b"* 0\ntrailer";
assert_eq!(param_star(p), IResult::Done(&b"trailer"[..], hashmap! {}));
assert_eq!(param_star(p), IResult::Done(&b"trailer"[..], hashmap!{}));
let p = b"* 1\n\
foo 12\n\
@ -690,7 +614,7 @@ mod test {
// no trailer
let p = b"* 0\n";
assert_eq!(param_star(p), IResult::Done(&b""[..], hashmap! {}));
assert_eq!(param_star(p), IResult::Done(&b""[..], hashmap!{}));
let p = b"* 1\n\
foo 12\n\
@ -885,7 +809,7 @@ mod test {
batch_params(p, 0),
IResult::Done(
&b""[..],
hashmap! {
hashmap!{
b"foo".to_vec() => b"bar".to_vec(),
}
)
@ -897,7 +821,7 @@ mod test {
batch_params(p, 0),
IResult::Done(
&b""[..],
hashmap! {
hashmap!{
b"foo".to_vec() => b"bar".to_vec(),
b"biff".to_vec() => b"bop".to_vec(),
b"esc:,;=".to_vec() => b"esc:,;=".to_vec(),
@ -907,13 +831,13 @@ mod test {
let p = b"";
assert_eq!(batch_params(p, 0), IResult::Done(&b""[..], hashmap! {}));
assert_eq!(batch_params(p, 0), IResult::Done(&b""[..], hashmap!{}));
let p = b"foo=";
assert_eq!(
batch_params(p, 0),
IResult::Done(&b""[..], hashmap! {b"foo".to_vec() => b"".to_vec()})
IResult::Done(&b""[..], hashmap!{b"foo".to_vec() => b"".to_vec()})
);
}
@ -1290,7 +1214,7 @@ mod test_parse {
Request::Single(SingleRequest::Getbundle(GetbundleArgs {
heads: vec![],
common: vec![],
bundlecaps: HashMap::new(),
bundlecaps: hashset![],
listkeys: vec![],
phases: false,
})),
@ -1317,57 +1241,13 @@ mod test_parse {
Request::Single(SingleRequest::Getbundle(GetbundleArgs {
heads: vec![hash_ones()],
common: vec![hash_twos(), hash_threes()],
bundlecaps: ["cap1", "CAP2", "cap3"]
.iter()
.map(|s| (s.to_string(), HashMap::new()))
.collect(),
bundlecaps: hashset![b"cap1".to_vec(), b"CAP2".to_vec(), b"cap3".to_vec()],
listkeys: vec![b"key1".to_vec(), b"key2".to_vec()],
phases: true,
})),
);
}
#[test]
fn test_parse_getbundle_bundlecaps() {
let inp = "getbundle\n\
* 1\n\
bundlecaps 51\n\
block,entries=entry%0Alist%3D1%2Cx%2Cy%0Asingle%3Dx";
let expected_entries_list: HashSet<String> =
["1", "x", "y"].iter().map(|x| x.to_string()).collect();
let expected_single: HashSet<String> = ["x"].iter().map(|x| x.to_string()).collect();
let expected_entries: HashMap<String, HashSet<String>> = [
("entry".to_string(), HashSet::new()),
("list".to_string(), expected_entries_list),
("single".to_string(), expected_single),
]
.iter()
.cloned()
.collect();
let expected_bundlecaps: HashMap<String, HashMap<String, HashSet<String>>> = [
("block".to_string(), HashMap::new()),
("entries".to_string(), expected_entries),
]
.iter()
.cloned()
.collect();
test_parse(
inp,
Request::Single(SingleRequest::Getbundle(GetbundleArgs {
heads: vec![],
common: vec![],
bundlecaps: expected_bundlecaps,
listkeys: vec![],
phases: false,
})),
);
}
#[test]
fn test_parse_heads() {
let inp = "heads\n";

View File

@ -338,13 +338,9 @@ impl RepoClient {
.map(|head| HgChangesetId::new(head))
.collect(),
self.lca_hint.clone(),
if args.phases
&& args.bundlecaps
.get("bundle2")
.and_then(|x| x.get("phases"))
.filter(|x| x.contains("heads"))
.is_some()
{
// TODO (liubovd): We will need to check that common phases exchange method is supported
// T38356449
if args.phases {
Some(self.phases_hint.clone())
} else {
None
@ -718,7 +714,7 @@ impl HgCommands for RepoClient {
info!(self.ctx.logger(), "Getbundle: {:?}", args);
let value = json!({
"bundlecaps": &args.bundlecaps,
"bundlecaps": format_utf8_bytes_list(&args.bundlecaps),
"common": format_nodes_list(&args.common),
"heads": format_nodes_list(&args.heads),
"listkeys": format_utf8_bytes_list(&args.listkeys),