…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 | } | ||