237 lines · 8.9 KB · Rust
RawDownload
use bstr::{BString, ByteSlice}; use crate::fetch::{refs::parse::Error, Ref}; impl From<InternalRef> for Ref {    fn from(v: InternalRef) -> Self {        match v {            InternalRef::Symbolic {                path,                target: Some(target),                object,            } => Ref::Symbolic {                full_ref_name: path,                target,                object,            },            InternalRef::Symbolic {                path,                target: None,                object,            } => Ref::Direct {                full_ref_name: path,                object,            },            InternalRef::Peeled { path, tag, object } => Ref::Peeled {                full_ref_name: path,                tag,                object,            },            InternalRef::Direct { path, object } => Ref::Direct {                full_ref_name: path,                object,            },            InternalRef::SymbolicForLookup { .. } => {                unreachable!("this case should have been removed during processing")            }        }    }} #[cfg_attr(test, derive(PartialEq, Eq, Debug, Clone))]pub(crate) enum InternalRef {    /// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit    Peeled {        path: BString,        tag: git_hash::ObjectId,        object: git_hash::ObjectId,    },    /// A ref pointing to a commit object    Direct { path: BString, object: git_hash::ObjectId },    /// A symbolic ref pointing to `target` ref, which in turn points to an `object`    Symbolic {        path: BString,        /// It is `None` if the target is unreachable as it points to another namespace than the one is currently set        /// on the server (i.e. based on the repository at hand or the user performing the operation).        ///        /// The latter is more of an edge case, please [this issue][#205] for details.        target: Option<BString>,        object: git_hash::ObjectId,    },    /// extracted from V1 capabilities, which contain some important symbolic refs along with their targets    /// These don't contain the Id    SymbolicForLookup { path: BString, target: Option<BString> },} impl InternalRef {    fn unpack_direct(self) -> Option<(BString, git_hash::ObjectId)> {        match self {            InternalRef::Direct { path, object } => Some((path, object)),            _ => None,        }    }    fn lookup_symbol_has_path(&self, predicate_path: &str) -> bool {        matches!(self, InternalRef::SymbolicForLookup { path, .. } if path == predicate_path)    }} pub(crate) fn from_capabilities<'a>(    capabilities: impl Iterator<Item = git_transport::client::capabilities::Capability<'a>>,) -> Result<Vec<InternalRef>, Error> {    let mut out_refs = Vec::new();    let symref_values = capabilities.filter_map(|c| {        if c.name() == b"symref".as_bstr() {            c.value().map(ToOwned::to_owned)        } else {            None        }    });    for symref in symref_values {        let (left, right) = symref.split_at(symref.find_byte(b':').ok_or_else(|| Error::MalformedSymref {            symref: symref.to_owned(),        })?);        if left.is_empty() || right.is_empty() {            return Err(Error::MalformedSymref {                symref: symref.to_owned(),            });        }        out_refs.push(InternalRef::SymbolicForLookup {            path: left.into(),            target: match &right[1..] {                b"(null)" => None,                name => Some(name.into()),            },        })    }    Ok(out_refs)} pub(in crate::fetch::refs) fn parse_v1(    num_initial_out_refs: usize,    out_refs: &mut Vec<InternalRef>,    line: &str,) -> Result<(), Error> {    let trimmed = line.trim_end();    let (hex_hash, path) = trimmed.split_at(        trimmed            .find(' ')            .ok_or_else(|| Error::MalformedV1RefLine(trimmed.to_owned()))?,    );    let path = &path[1..];    if path.is_empty() {        return Err(Error::MalformedV1RefLine(trimmed.to_owned()));    }    match path.strip_suffix("^{}") {        Some(stripped) => {            let (previous_path, tag) =                out_refs                    .pop()                    .and_then(InternalRef::unpack_direct)                    .ok_or(Error::InvariantViolation {                        message: "Expecting peeled refs to be preceded by direct refs",                    })?;            if previous_path != stripped {                return Err(Error::InvariantViolation {                    message: "Expecting peeled refs to have the same base path as the previous, unpeeled one",                });            }            out_refs.push(InternalRef::Peeled {                path: previous_path,                tag,                object: git_hash::ObjectId::from_hex(hex_hash.as_bytes())?,            });        }        None => {            let object = git_hash::ObjectId::from_hex(hex_hash.as_bytes())?;            match out_refs                .iter()                .take(num_initial_out_refs)                .position(|r| r.lookup_symbol_has_path(path))            {                Some(position) => match out_refs.swap_remove(position) {                    InternalRef::SymbolicForLookup { path: _, target } => out_refs.push(InternalRef::Symbolic {                        path: path.into(),                        object,                        target,                    }),                    _ => unreachable!("Bug in lookup_symbol_has_path - must return lookup symbols"),                },                None => out_refs.push(InternalRef::Direct {                    object,                    path: path.into(),                }),            };        }    }    Ok(())} pub(in crate::fetch::refs) fn parse_v2(line: &str) -> Result<Ref, Error> {    let trimmed = line.trim_end();    let mut tokens = trimmed.splitn(3, ' ');    match (tokens.next(), tokens.next()) {        (Some(hex_hash), Some(path)) => {            let id = if hex_hash == "unborn" {                None            } else {                Some(git_hash::ObjectId::from_hex(hex_hash.as_bytes())?)            };            if path.is_empty() {                return Err(Error::MalformedV2RefLine(trimmed.to_owned()));            }            Ok(if let Some(attribute) = tokens.next() {                let mut tokens = attribute.splitn(2, ':');                match (tokens.next(), tokens.next()) {                    (Some(attribute), Some(value)) => {                        if value.is_empty() {                            return Err(Error::MalformedV2RefLine(trimmed.to_owned()));                        }                        match attribute {                            "peeled" => Ref::Peeled {                                full_ref_name: path.into(),                                object: git_hash::ObjectId::from_hex(value.as_bytes())?,                                tag: id.ok_or(Error::InvariantViolation {                                    message: "got 'unborn' as tag target",                                })?,                            },                            "symref-target" => match value {                                "(null)" => Ref::Direct {                                    full_ref_name: path.into(),                                    object: id.ok_or(Error::InvariantViolation {                                        message: "got 'unborn' while (null) was a symref target",                                    })?,                                },                                name => match id {                                    Some(id) => Ref::Symbolic {                                        full_ref_name: path.into(),                                        object: id,                                        target: name.into(),                                    },                                    None => Ref::Unborn {                                        full_ref_name: path.into(),                                        target: name.into(),                                    },                                },                            },                            _ => {                                return Err(Error::UnkownAttribute {                                    attribute: attribute.to_owned(),                                    line: trimmed.to_owned(),                                })                            }                        }                    }                    _ => return Err(Error::MalformedV2RefLine(trimmed.to_owned())),                }            } else {                Ref::Direct {                    object: id.ok_or(Error::InvariantViolation {                        message: "got 'unborn' as object name of direct reference",                    })?,                    full_ref_name: path.into(),                }            })        }        _ => Err(Error::MalformedV2RefLine(trimmed.to_owned())),    }}