…into async-io, as usual.
Showing the changes to git-protocol/src/fetch/refs.rs (1 of 12 files). Show all
Unified · Split
| @@ -1,271 +0,0 @@ | ||
| 1 | −use bstr::{BString, ByteSlice}; | |
| 2 | −use quick_error::quick_error; | |
| 3 | −use std::io; | |
| 4 | − | |
| 5 | −quick_error! { | |
| 6 | − #[derive(Debug)] | |
| 7 | − pub enum Error { | |
| 8 | − Io(err: io::Error) { | |
| 9 | − display("An IO error occurred while reading refs from the server") | |
| 10 | − from() | |
| 11 | − source(err) | |
| 12 | − } | |
| 13 | − Id(err: git_hash::decode::Error) { | |
| 14 | − display("Failed to hex-decode object hash") | |
| 15 | − from() | |
| 16 | − source(err) | |
| 17 | − } | |
| 18 | − MalformedSymref(symref: BString) { | |
| 19 | − display("'{}' could not be parsed. A symref is expected to look like <NAME>:<target>.", symref) | |
| 20 | − } | |
| 21 | − MalformedV1RefLine(line: String) { | |
| 22 | − display("'{}' could not be parsed. A V1 ref line should be '<hex-hash> <path>'.", line) | |
| 23 | − } | |
| 24 | − MalformedV2RefLine(line: String) { | |
| 25 | − display("'{}' could not be parsed. A V2 ref line should be '<hex-hash> <path>[ (peeled|symref-target):<value>'.", line) | |
| 26 | − } | |
| 27 | − UnkownAttribute(attribute: String, line: String) { | |
| 28 | − display("The ref attribute '{}' is unknown. Found in line '{}'", attribute, line) | |
| 29 | − } | |
| 30 | − InvariantViolation(message: &'static str) { | |
| 31 | − display("{}", message) | |
| 32 | − } | |
| 33 | − } | |
| 34 | −} | |
| 35 | − | |
| 36 | −/// A git reference, commonly referred to as 'ref', as returned by a git server before sending a pack. | |
| 37 | −#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | |
| 38 | −#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | |
| 39 | −pub enum Ref { | |
| 40 | − /// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit | |
| 41 | − Peeled { | |
| 42 | − /// The path at which the ref is located, like `/refs/heads/main`. | |
| 43 | − path: BString, | |
| 44 | − /// The hash of the tag the ref points to. | |
| 45 | − tag: git_hash::ObjectId, | |
| 46 | − /// The hash of the object the `tag` points to. | |
| 47 | − object: git_hash::ObjectId, | |
| 48 | − }, | |
| 49 | − /// A ref pointing to a commit object | |
| 50 | − Direct { | |
| 51 | − /// The path at which the ref is located, like `/refs/heads/main`. | |
| 52 | − path: BString, | |
| 53 | − /// The hash of the object the ref points to. | |
| 54 | − object: git_hash::ObjectId, | |
| 55 | − }, | |
| 56 | − /// A symbolic ref pointing to `target` ref, which in turn points to an `object` | |
| 57 | − Symbolic { | |
| 58 | − /// The path at which the symbolic ref is located, like `/refs/heads/main`. | |
| 59 | − path: BString, | |
| 60 | − /// The path of the ref the symbolic ref points to. | |
| 61 | − target: BString, | |
| 62 | − /// The hash of the object the `target` ref points to. | |
| 63 | − object: git_hash::ObjectId, | |
| 64 | − }, | |
| 65 | −} | |
| 66 | − | |
| 67 | −impl Ref { | |
| 68 | − /// Provide shared fields referring to the ref itself, namely `(path, object id)`. | |
| 69 | − /// In case of peeled refs, the tag object itself is returned as it is what the path refers to. | |
| 70 | − pub fn unpack(&self) -> (&BString, &git_hash::ObjectId) { | |
| 71 | − match self { | |
| 72 | − Ref::Direct { path, object, .. } | |
| 73 | − | Ref::Peeled { path, tag: object, .. } // the tag acts as reference | |
| 74 | − | Ref::Symbolic { path, object, .. } => (path, object), | |
| 75 | − } | |
| 76 | − } | |
| 77 | −} | |
| 78 | − | |
| 79 | −impl From<InternalRef> for Ref { | |
| 80 | − fn from(v: InternalRef) -> Self { | |
| 81 | − match v { | |
| 82 | − InternalRef::Symbolic { path, target, object } => Ref::Symbolic { path, target, object }, | |
| 83 | − InternalRef::Peeled { path, tag, object } => Ref::Peeled { path, tag, object }, | |
| 84 | − InternalRef::Direct { path, object } => Ref::Direct { path, object }, | |
| 85 | − InternalRef::SymbolicForLookup { .. } => { | |
| 86 | − unreachable!("this case should have been removed during processing") | |
| 87 | − } | |
| 88 | − } | |
| 89 | − } | |
| 90 | −} | |
| 91 | − | |
| 92 | −#[cfg_attr(test, derive(PartialEq, Eq, Debug, Clone))] | |
| 93 | −pub(crate) enum InternalRef { | |
| 94 | − /// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit | |
| 95 | − Peeled { | |
| 96 | − path: BString, | |
| 97 | − tag: git_hash::ObjectId, | |
| 98 | − object: git_hash::ObjectId, | |
| 99 | − }, | |
| 100 | − /// A ref pointing to a commit object | |
| 101 | − Direct { path: BString, object: git_hash::ObjectId }, | |
| 102 | − /// A symbolic ref pointing to `target` ref, which in turn points to an `object` | |
| 103 | − Symbolic { | |
| 104 | − path: BString, | |
| 105 | − target: BString, | |
| 106 | − object: git_hash::ObjectId, | |
| 107 | − }, | |
| 108 | − /// extracted from V1 capabilities, which contain some important symbolic refs along with their targets | |
| 109 | − /// These don't contain the Id | |
| 110 | − SymbolicForLookup { path: BString, target: BString }, | |
| 111 | −} | |
| 112 | − | |
| 113 | −impl InternalRef { | |
| 114 | − fn unpack_direct(self) -> Option<(BString, git_hash::ObjectId)> { | |
| 115 | − match self { | |
| 116 | − InternalRef::Direct { path, object } => Some((path, object)), | |
| 117 | − _ => None, | |
| 118 | − } | |
| 119 | − } | |
| 120 | − fn lookup_symbol_has_path(&self, predicate_path: &str) -> bool { | |
| 121 | − matches!(self, InternalRef::SymbolicForLookup { path, .. } if path == predicate_path) | |
| 122 | − } | |
| 123 | −} | |
| 124 | − | |
| 125 | −pub(crate) fn from_capabilities<'a>( | |
| 126 | − out_refs: &mut Vec<InternalRef>, | |
| 127 | − capabilities: impl Iterator<Item = git_transport::client::capabilities::Capability<'a>>, | |
| 128 | −) -> Result<(), Error> { | |
| 129 | − let symref_values = capabilities.filter_map(|c| { | |
| 130 | − if c.name() == b"symref".as_bstr() { | |
| 131 | − c.value().map(ToOwned::to_owned) | |
| 132 | − } else { | |
| 133 | − None | |
| 134 | − } | |
| 135 | − }); | |
| 136 | − for symref in symref_values { | |
| 137 | − let (left, right) = symref.split_at( | |
| 138 | − symref | |
| 139 | − .find_byte(b':') | |
| 140 | − .ok_or_else(|| Error::MalformedSymref(symref.to_owned()))?, | |
| 141 | − ); | |
| 142 | − if left.is_empty() || right.is_empty() { | |
| 143 | − return Err(Error::MalformedSymref(symref.to_owned())); | |
| 144 | − } | |
| 145 | − out_refs.push(InternalRef::SymbolicForLookup { | |
| 146 | − path: left.into(), | |
| 147 | − target: right[1..].into(), | |
| 148 | − }) | |
| 149 | − } | |
| 150 | − Ok(()) | |
| 151 | −} | |
| 152 | − | |
| 153 | −pub(crate) fn from_v2_refs(out_refs: &mut Vec<Ref>, in_refs: &mut dyn io::BufRead) -> Result<(), Error> { | |
| 154 | − let mut line = String::new(); | |
| 155 | − loop { | |
| 156 | − line.clear(); | |
| 157 | − let bytes_read = in_refs.read_line(&mut line)?; | |
| 158 | − if bytes_read == 0 { | |
| 159 | − break; | |
| 160 | − } | |
| 161 | − let trimmed = line.trim_end(); | |
| 162 | − let mut tokens = trimmed.splitn(3, ' '); | |
| 163 | − match (tokens.next(), tokens.next()) { | |
| 164 | − (Some(hex_hash), Some(path)) => { | |
| 165 | − let id = git_hash::ObjectId::from_hex(hex_hash.as_bytes())?; | |
| 166 | − if path.is_empty() { | |
| 167 | − return Err(Error::MalformedV2RefLine(trimmed.to_owned())); | |
| 168 | − } | |
| 169 | − out_refs.push(if let Some(attribute) = tokens.next() { | |
| 170 | − let mut tokens = attribute.splitn(2, ':'); | |
| 171 | − match (tokens.next(), tokens.next()) { | |
| 172 | − (Some(attribute), Some(value)) => { | |
| 173 | − if value.is_empty() { | |
| 174 | − return Err(Error::MalformedV2RefLine(trimmed.to_owned())); | |
| 175 | − } | |
| 176 | − match attribute { | |
| 177 | − "peeled" => Ref::Peeled { | |
| 178 | − path: path.into(), | |
| 179 | − object: git_hash::ObjectId::from_hex(value.as_bytes())?, | |
| 180 | − tag: id, | |
| 181 | − }, | |
| 182 | − "symref-target" => Ref::Symbolic { | |
| 183 | − path: path.into(), | |
| 184 | − object: id, | |
| 185 | − target: value.into(), | |
| 186 | − }, | |
| 187 | − _ => return Err(Error::UnkownAttribute(attribute.to_owned(), trimmed.to_owned())), | |
| 188 | − } | |
| 189 | − } | |
| 190 | − _ => return Err(Error::MalformedV2RefLine(trimmed.to_owned())), | |
| 191 | − } | |
| 192 | − } else { | |
| 193 | − Ref::Direct { | |
| 194 | − object: id, | |
| 195 | − path: path.into(), | |
| 196 | − } | |
| 197 | − }); | |
| 198 | − } | |
| 199 | − _ => return Err(Error::MalformedV2RefLine(trimmed.to_owned())), | |
| 200 | − } | |
| 201 | − } | |
| 202 | − Ok(()) | |
| 203 | −} | |
| 204 | − | |
| 205 | −pub(crate) fn from_v1_refs_received_as_part_of_handshake( | |
| 206 | − out_refs: &mut Vec<InternalRef>, | |
| 207 | − in_refs: &mut dyn io::BufRead, | |
| 208 | −) -> Result<(), Error> { | |
| 209 | − let number_of_possible_symbolic_refs_for_lookup = out_refs.len(); | |
| 210 | − let mut line = String::new(); | |
| 211 | − loop { | |
| 212 | − line.clear(); | |
| 213 | − let bytes_read = in_refs.read_line(&mut line)?; | |
| 214 | − if bytes_read == 0 { | |
| 215 | − break; | |
| 216 | − } | |
| 217 | − let trimmed = line.trim_end(); | |
| 218 | − let (hex_hash, path) = trimmed.split_at( | |
| 219 | − trimmed | |
| 220 | − .find(' ') | |
| 221 | − .ok_or_else(|| Error::MalformedV1RefLine(trimmed.to_owned()))?, | |
| 222 | − ); | |
| 223 | − let path = &path[1..]; | |
| 224 | − if path.is_empty() { | |
| 225 | − return Err(Error::MalformedV1RefLine(trimmed.to_owned())); | |
| 226 | − } | |
| 227 | − match path.strip_suffix("^{}") { | |
| 228 | − Some(stripped) => { | |
| 229 | − let (previous_path, tag) = | |
| 230 | − out_refs | |
| 231 | − .pop() | |
| 232 | − .and_then(InternalRef::unpack_direct) | |
| 233 | − .ok_or(Error::InvariantViolation( | |
| 234 | − "Expecting peeled refs to be preceded by direct refs", | |
| 235 | − ))?; | |
| 236 | − if previous_path != stripped { | |
| 237 | − return Err(Error::InvariantViolation( | |
| 238 | − "Expecting peeled refs to have the same base path as the previous, unpeeled one", | |
| 239 | − )); | |
| 240 | − } | |
| 241 | − out_refs.push(InternalRef::Peeled { | |
| 242 | − path: previous_path, | |
| 243 | − tag, | |
| 244 | − object: git_hash::ObjectId::from_hex(hex_hash.as_bytes())?, | |
| 245 | − }); | |
| 246 | − } | |
| 247 | − None => { | |
| 248 | − let object = git_hash::ObjectId::from_hex(hex_hash.as_bytes())?; | |
| 249 | − match out_refs | |
| 250 | − .iter() | |
| 251 | − .take(number_of_possible_symbolic_refs_for_lookup) | |
| 252 | − .position(|r| r.lookup_symbol_has_path(path)) | |
| 253 | − { | |
| 254 | − Some(position) => match out_refs.swap_remove(position) { | |
| 255 | − InternalRef::SymbolicForLookup { path: _, target } => out_refs.push(InternalRef::Symbolic { | |
| 256 | − path: path.into(), | |
| 257 | − object, | |
| 258 | − target, | |
| 259 | − }), | |
| 260 | − _ => unreachable!("Bug in lookup_symbol_has_path - must return lookup symbols"), | |
| 261 | − }, | |
| 262 | − None => out_refs.push(InternalRef::Direct { | |
| 263 | − object, | |
| 264 | − path: path.into(), | |
| 265 | − }), | |
| 266 | − }; | |
| 267 | − } | |
| 268 | − } | |
| 269 | − } | |
| 270 | − Ok(()) | |
| 271 | −} | |