…into async-io, as usual.
Showing the changes to git-protocol/src/fetch/command.rs (1 of 12 files). Show all
Unified · Split
| @@ -1,196 +0,0 @@ | |||
| 1 | use crate::fetch::agent; | ||
| 2 | use bstr::{BString, ByteSlice}; | ||
| 3 | use git_transport::client::Capabilities; | ||
| 4 | |||
| 5 | /// The kind of command to invoke on the server side. | ||
| 6 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] | ||
| 7 | pub enum Command { | ||
| 8 | /// List references. | ||
| 9 | LsRefs, | ||
| 10 | /// Fetch a pack. | ||
| 11 | Fetch, | ||
| 12 | } | ||
| 13 | |||
| 14 | /// A key value pair of values known at compile time. | ||
| 15 | pub type Feature = (&'static str, Option<&'static str>); | ||
| 16 | |||
| 17 | impl Command { | ||
| 18 | /// Produce the name of the command as known by the server side. | ||
| 19 | pub fn as_str(&self) -> &'static str { | ||
| 20 | match self { | ||
| 21 | Command::LsRefs => "ls-refs", | ||
| 22 | Command::Fetch => "fetch", | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | /// Only V2 | ||
| 27 | fn all_argument_prefixes(&self) -> &'static [&'static str] { | ||
| 28 | match self { | ||
| 29 | Command::LsRefs => &["symrefs", "peel", "ref-prefix "], | ||
| 30 | Command::Fetch => &[ | ||
| 31 | "want ", // hex oid | ||
| 32 | "have ", // hex oid | ||
| 33 | "done", | ||
| 34 | "thin-pack", | ||
| 35 | "no-progress", | ||
| 36 | "include-tag", | ||
| 37 | "ofs-delta", | ||
| 38 | // Shallow feature/capability | ||
| 39 | "shallow ", // hex oid | ||
| 40 | "deepen ", // commit depth | ||
| 41 | "deepen-relative", | ||
| 42 | "deepen-since ", // time-stamp | ||
| 43 | "deepen-not ", // rev | ||
| 44 | // filter feature/capability | ||
| 45 | "filter ", // filter-spec | ||
| 46 | // ref-in-want feature | ||
| 47 | "want-ref ", // ref path | ||
| 48 | "sideband-all", | ||
| 49 | // packfile-uris feature | ||
| 50 | "packfile-uris ", // protocols | ||
| 51 | ], | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | fn all_features(&self, version: git_transport::Protocol) -> &'static [&'static str] { | ||
| 56 | match self { | ||
| 57 | Command::LsRefs => &[], | ||
| 58 | Command::Fetch => match version { | ||
| 59 | git_transport::Protocol::V1 => &[ | ||
| 60 | "multi_ack", | ||
| 61 | "thin-pack", | ||
| 62 | "side-band", | ||
| 63 | "side-band-64k", | ||
| 64 | "ofs-delta", | ||
| 65 | "shallow", | ||
| 66 | "deepen-since", | ||
| 67 | "deepen-not", | ||
| 68 | "deepen-relative", | ||
| 69 | "no-progress", | ||
| 70 | "include-tag", | ||
| 71 | "multi_ack_detailed", | ||
| 72 | "allow-tip-sha1-in-want", | ||
| 73 | "allow-reachable-sha1-in-want", | ||
| 74 | "no-done", | ||
| 75 | "filter", | ||
| 76 | ], | ||
| 77 | git_transport::Protocol::V2 => &["shallow", "filter", "ref-in-want", "sideband-all", "packfile-uris"], | ||
| 78 | }, | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | /// Compute initial arguments based on the given `features`. They are typically provided by the `default_features(…)` method. | ||
| 83 | /// Only useful for V2 | ||
| 84 | pub(crate) fn initial_arguments(&self, features: &[Feature]) -> Vec<BString> { | ||
| 85 | match self { | ||
| 86 | Command::Fetch => ["thin-pack", "include-tag", "ofs-delta"] | ||
| 87 | .iter() | ||
| 88 | .map(|s| s.as_bytes().as_bstr().to_owned()) | ||
| 89 | .chain( | ||
| 90 | ["sideband-all", "ref-in-want", "packfile-uris"] | ||
| 91 | .iter() | ||
| 92 | .filter(|f| features.iter().any(|(sf, _)| sf == *f)) | ||
| 93 | .map(|f| f.as_bytes().as_bstr().to_owned()), | ||
| 94 | ) | ||
| 95 | .collect(), | ||
| 96 | Command::LsRefs => vec![b"symrefs".as_bstr().to_owned(), b"peel".as_bstr().to_owned()], | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | pub(crate) fn default_features( | ||
| 101 | &self, | ||
| 102 | version: git_transport::Protocol, | ||
| 103 | server_capabilities: &Capabilities, | ||
| 104 | ) -> Vec<Feature> { | ||
| 105 | match self { | ||
| 106 | Command::Fetch => match version { | ||
| 107 | git_transport::Protocol::V1 => { | ||
| 108 | let has_multi_ack_detailed = server_capabilities.contains("multi_ack_detailed"); | ||
| 109 | let has_sideband_64k = server_capabilities.contains("side-band-64k"); | ||
| 110 | self.all_features(version) | ||
| 111 | .iter() | ||
| 112 | .copied() | ||
| 113 | .filter(|feature| match *feature { | ||
| 114 | "side-band" if has_sideband_64k => false, | ||
| 115 | "multi_ack" if has_multi_ack_detailed => false, | ||
| 116 | "include-tag" | "no-progress" => false, | ||
| 117 | feature => server_capabilities.contains(feature), | ||
| 118 | }) | ||
| 119 | .map(|s| (s, None)) | ||
| 120 | .chain(Some(agent())) | ||
| 121 | .collect() | ||
| 122 | } | ||
| 123 | git_transport::Protocol::V2 => { | ||
| 124 | let supported_features = server_capabilities | ||
| 125 | .iter() | ||
| 126 | .find_map(|c| { | ||
| 127 | if c.name() == Command::Fetch.as_str().as_bytes().as_bstr() { | ||
| 128 | c.values().map(|v| v.map(|f| f.to_owned()).collect()) | ||
| 129 | } else { | ||
| 130 | None | ||
| 131 | } | ||
| 132 | }) | ||
| 133 | .unwrap_or_else(Vec::new); | ||
| 134 | self.all_features(version) | ||
| 135 | .iter() | ||
| 136 | .copied() | ||
| 137 | .filter(|feature| supported_features.iter().any(|supported| supported == feature)) | ||
| 138 | .map(|s| (s, None)) | ||
| 139 | .chain(Some(agent())) | ||
| 140 | .collect() | ||
| 141 | } | ||
| 142 | }, | ||
| 143 | Command::LsRefs => vec![agent()], | ||
| 144 | } | ||
| 145 | } | ||
| 146 | /// Panics if the given arguments and features don't match what's statically known. It's considered a bug in the delegate. | ||
| 147 | pub(crate) fn validate_argument_prefixes_or_panic( | ||
| 148 | &self, | ||
| 149 | version: git_transport::Protocol, | ||
| 150 | server: &Capabilities, | ||
| 151 | arguments: &[BString], | ||
| 152 | features: &[Feature], | ||
| 153 | ) { | ||
| 154 | let allowed = self.all_argument_prefixes(); | ||
| 155 | for arg in arguments { | ||
| 156 | if allowed.iter().any(|allowed| arg.starts_with(allowed.as_bytes())) { | ||
| 157 | continue; | ||
| 158 | } | ||
| 159 | panic!("{}: argument {} is not known or allowed", self.as_str(), arg); | ||
| 160 | } | ||
| 161 | match version { | ||
| 162 | git_transport::Protocol::V1 => { | ||
| 163 | for (feature, _) in features { | ||
| 164 | if server | ||
| 165 | .iter() | ||
| 166 | .any(|c| feature.starts_with(c.name().to_str_lossy().as_ref())) | ||
| 167 | { | ||
| 168 | continue; | ||
| 169 | } | ||
| 170 | panic!("{}: capability {} is not supported", self.as_str(), feature); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | git_transport::Protocol::V2 => { | ||
| 174 | let allowed = server | ||
| 175 | .iter() | ||
| 176 | .find_map(|c| { | ||
| 177 | if c.name() == self.as_str().as_bytes().as_bstr() { | ||
| 178 | c.values().map(|v| v.map(|f| f.to_string()).collect::<Vec<_>>()) | ||
| 179 | } else { | ||
| 180 | None | ||
| 181 | } | ||
| 182 | }) | ||
| 183 | .unwrap_or_default(); | ||
| 184 | for (feature, _) in features { | ||
| 185 | if allowed.iter().any(|allowed| feature == allowed) { | ||
| 186 | continue; | ||
| 187 | } | ||
| 188 | match *feature { | ||
| 189 | "agent" => {} | ||
| 190 | _ => panic!("{}: V2 feature/capability {} is not supported", self.as_str(), feature), | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||