1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
//! V2 command abstraction to validate invocations and arguments, like a database of what we know about them.
use std::borrow::Cow;
use super::Command;
/// A key value pair of values known at compile time.
pub type Feature = (&'static str, Option<Cow<'static, str>>);
impl Command {
/// Produce the name of the command as known by the server side.
pub fn as_str(&self) -> &'static str {
match self {
Command::LsRefs => "ls-refs",
Command::Fetch => "fetch",
}
}
}
#[cfg(any(test, feature = "async-client", feature = "blocking-client"))]
mod with_io {
use bstr::{BString, ByteSlice};
use gix_transport::client::Capabilities;
use crate::{command::Feature, Command};
impl Command {
/// Only V2
fn all_argument_prefixes(&self) -> &'static [&'static str] {
match self {
Command::LsRefs => &["symrefs", "peel", "ref-prefix ", "unborn"],
Command::Fetch => &[
"want ", // hex oid
"have ", // hex oid
"done",
"thin-pack",
"no-progress",
"include-tag",
"ofs-delta",
// Shallow feature/capability
"shallow ", // hex oid
"deepen ", // commit depth
"deepen-relative",
"deepen-since ", // time-stamp
"deepen-not ", // rev
// filter feature/capability
"filter ", // filter-spec
// ref-in-want feature
"want-ref ", // ref path
// sideband-all feature
"sideband-all",
// packfile-uris feature
"packfile-uris ", // protocols
// wait-for-done feature
"wait-for-done",
],
}
}
fn all_features(&self, version: gix_transport::Protocol) -> &'static [&'static str] {
match self {
Command::LsRefs => &[],
Command::Fetch => match version {
gix_transport::Protocol::V0 | gix_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",
],
gix_transport::Protocol::V2 => &[
"shallow",
"filter",
"ref-in-want",
"sideband-all",
"packfile-uris",
"wait-for-done",
],
},
}
}
/// Compute initial arguments based on the given `features`. They are typically provided by the `default_features(…)` method.
/// Only useful for V2
pub(crate) fn initial_arguments(&self, features: &[Feature]) -> Vec<BString> {
match self {
Command::Fetch => ["thin-pack", "ofs-delta"]
.iter()
.map(|s| s.as_bytes().as_bstr().to_owned())
.chain(
[
"sideband-all",
/* "packfile-uris" */ // packfile-uris must be configurable and can't just be used. Some servers advertise it and reject it later.
]
.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()],
}
}
/// Turns on all modern features for V1 and all supported features for V2, returning them as a vector of features.
/// Note that this is the basis for any fetch operation as these features fulfil basic requirements and reasonably up-to-date servers.
pub fn default_features(
&self,
version: gix_transport::Protocol,
server_capabilities: &Capabilities,
) -> Vec<Feature> {
match self {
Command::Fetch => match version {
gix_transport::Protocol::V0 | gix_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,
"no-progress" => false,
feature => server_capabilities.contains(feature),
})
.map(|s| (s, None))
.collect()
}
gix_transport::Protocol::V2 => {
let supported_features: Vec<_> = server_capabilities
.iter()
.find_map(|c| {
if c.name() == Command::Fetch.as_str() {
c.values().map(|v| v.map(ToOwned::to_owned).collect())
} else {
None
}
})
.unwrap_or_default();
self.all_features(version)
.iter()
.copied()
.filter(|feature| supported_features.iter().any(|supported| supported == feature))
.map(|s| (s, None))
.collect()
}
},
Command::LsRefs => vec![],
}
}
/// Panics if the given arguments and features don't match what's statically known. It's considered a bug in the delegate.
pub(crate) fn validate_argument_prefixes_or_panic(
&self,
version: gix_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 {
gix_transport::Protocol::V0 | gix_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);
}
}
gix_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(ToString::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),
}
}
}
}
}
}
}
#[cfg(test)]
mod tests;