use crate::fetch::agent;
use bstr::{BString, ByteSlice};
use git_transport::client::Capabilities;
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
pub enum Command {
LsRefs,
Fetch,
}
pub type Feature = (&'static str, Option<&'static str>);
impl Command {
pub fn as_str(&self) -> &'static str {
match self {
Command::LsRefs => "ls-refs",
Command::Fetch => "fetch",
}
}
fn all_argument_prefixes(&self) -> &'static [&'static str] {
match self {
Command::LsRefs => &["symrefs", "peel", "ref-prefix "],
Command::Fetch => &[
"want ", "have ", "done",
"thin-pack",
"no-progress",
"include-tag",
"ofs-delta",
"shallow ", "deepen ", "deepen-relative",
"deepen-since ", "deepen-not ", "filter ", "want-ref ", "sideband-all",
"packfile-uris ", ],
}
}
fn all_features(&self, version: git_transport::Protocol) -> &'static [&'static str] {
match self {
Command::LsRefs => &[],
Command::Fetch => match version {
git_transport::Protocol::V1 => &[
"multi_ack",
"thin-pack",
"side-band",
"side-band-64k",
"ofs-delta",
"shallow",
"deepen-since",
"deepen-not",
"deepen-relative",
"no-progress",
"include-tag",
"multi_ack_detailed",
"allow-tip-sha1-in-want",
"allow-reachable-sha1-in-want",
"no-done",
"filter",
],
git_transport::Protocol::V2 => &["shallow", "filter", "ref-in-want", "sideband-all", "packfile-uris"],
},
}
}
pub(crate) fn initial_arguments(&self, features: &[Feature]) -> Vec<BString> {
match self {
Command::Fetch => ["thin-pack", "include-tag", "ofs-delta"]
.iter()
.map(|s| s.as_bytes().as_bstr().to_owned())
.chain(
["sideband-all", "ref-in-want", "packfile-uris"]
.iter()
.filter(|f| features.iter().any(|(sf, _)| sf == *f))
.map(|f| f.as_bytes().as_bstr().to_owned()),
)
.collect(),
Command::LsRefs => vec![b"symrefs".as_bstr().to_owned(), b"peel".as_bstr().to_owned()],
}
}
pub(crate) fn default_features(
&self,
version: git_transport::Protocol,
server_capabilities: &Capabilities,
) -> Vec<Feature> {
match self {
Command::Fetch => match version {
git_transport::Protocol::V1 => {
let has_multi_ack_detailed = server_capabilities.contains("multi_ack_detailed");
let has_sideband_64k = server_capabilities.contains("side-band-64k");
self.all_features(version)
.iter()
.copied()
.filter(|feature| match *feature {
"side-band" if has_sideband_64k => false,
"multi_ack" if has_multi_ack_detailed => false,
"include-tag" | "no-progress" => false,
feature => server_capabilities.contains(feature),
})
.map(|s| (s, None))
.chain(Some(agent()))
.collect()
}
git_transport::Protocol::V2 => {
let supported_features = server_capabilities
.iter()
.find_map(|c| {
if c.name() == Command::Fetch.as_str().as_bytes().as_bstr() {
c.values().map(|v| v.map(|f| f.to_owned()).collect())
} else {
None
}
})
.unwrap_or_else(Vec::new);
self.all_features(version)
.iter()
.copied()
.filter(|feature| supported_features.iter().any(|supported| supported == feature))
.map(|s| (s, None))
.chain(Some(agent()))
.collect()
}
},
Command::LsRefs => vec![agent()],
}
}
pub(crate) fn validate_argument_prefixes_or_panic(
&self,
version: git_transport::Protocol,
server: &Capabilities,
arguments: &[BString],
features: &[Feature],
) {
let allowed = self.all_argument_prefixes();
for arg in arguments {
if allowed.iter().any(|allowed| arg.starts_with(allowed.as_bytes())) {
continue;
}
panic!("{}: argument {} is not known or allowed", self.as_str(), arg);
}
match version {
git_transport::Protocol::V1 => {
for (feature, _) in features {
if server
.iter()
.any(|c| feature.starts_with(c.name().to_str_lossy().as_ref()))
{
continue;
}
panic!("{}: capability {} is not supported", self.as_str(), feature);
}
}
git_transport::Protocol::V2 => {
let allowed = server
.iter()
.find_map(|c| {
if c.name() == self.as_str().as_bytes().as_bstr() {
c.values().map(|v| v.map(|f| f.to_string()).collect::<Vec<_>>())
} else {
None
}
})
.unwrap_or_default();
for (feature, _) in features {
if allowed.iter().any(|allowed| feature == allowed) {
continue;
}
match *feature {
"agent" => {}
_ => panic!("{}: V2 feature/capability {} is not supported", self.as_str(), feature),
}
}
}
}
}
}