GPT 5.5reviewa8b885d4 months agoHistory
154 lines · 5.1 KB · Rust
RawDownload
use gix_object::bstr::{BStr, ByteSlice}; use crate::{parse, store_impl::packed}; #[derive(Debug, PartialEq, Eq)]enum Peeled {    Unspecified,    Partial,    Fully,} /// Information parsed from the header of a packed ref file#[derive(Debug, PartialEq, Eq)]pub struct Header {    peeled: Peeled,    pub sorted: bool,} impl Default for Header {    fn default() -> Self {        Header {            peeled: Peeled::Unspecified,            sorted: false,        }    }} /// Return the bytes before the next line ending as a [`BStr`].////// On success, `input` is advanced past the line ending. The returned slice/// does not include the line ending.fn until_line_end_without_separator<'a>(input: &mut &'a [u8]) -> Result<&'a BStr, ()> {    let line_end = input.iter().position(|b| *b == b'\r' || *b == b'\n').ok_or(())?;    let out = input[..line_end].as_bstr();    let mut maybe_start_of_newline = &input[line_end..];    parse::newline(&mut maybe_start_of_newline)?;    *input = maybe_start_of_newline;    Ok(out)} /// Parse a `packed-refs` header line.////// A valid header starts with `# pack-refs with: ` and ends with a line ending./// Known space-separated traits after the prefix populate the returned/// [`Header`]: `peeled`, `fully-peeled`, and `sorted`. Unknown traits are/// ignored.////// On success, `input` is advanced past the entire header line, including its/// line ending.pub fn header(input: &mut &[u8]) -> Result<Header, ()> {    let Some(rest) = input.strip_prefix(b"# pack-refs with: ") else {        return Err(());    };    *input = rest;    let traits = until_line_end_without_separator(input)?;    let mut peeled = Peeled::Unspecified;    let mut sorted = false;    for token in traits.split_str(b" ") {        if token == b"fully-peeled" {            peeled = Peeled::Fully;        } else if token == b"peeled" {            peeled = Peeled::Partial;        } else if token == b"sorted" {            sorted = true;        }    }    Ok(Header { peeled, sorted })} /// Parse one packed reference entry and its optional peeled object line.////// The reference line has the form `<hex-object-id> <ref-name>` followed by a/// line ending. If the following line starts with `^`, it is parsed as the/// peeled object id for the returned [`packed::Reference`]./// Object ids are parsed according to `object_hash`.////// On success, `input` is advanced past the reference line and, if present, the/// peeled object line.pub fn reference<'a>(input: &mut &'a [u8], object_hash: gix_hash::Kind) -> Result<packed::Reference<'a>, ()> {    let target = parse::hex_hash(input, object_hash)?;    let Some(rest) = input.strip_prefix(b" ") else {        return Err(());    };    *input = rest;    let name = until_line_end_without_separator(input)?.try_into().map_err(|_| ())?;     let object = if let Some(rest) = input.strip_prefix(b"^") {        *input = rest;        let object = parse::hex_hash(input, object_hash)?;        parse::newline(input)?;        Some(object)    } else {        None    };     Ok(packed::Reference { name, target, object })} /// Extract the name bytes from a packed-refs record without validating the/// name as a [`FullNameRef`][crate::FullNameRef] or the hash as hex.////// Used by the binary search in [`super::Buffer::binary_search_by`], where/// only the name bytes are needed for ordered comparison — the final match/// is re-parsed through [`reference`] which validates fully.////// Returns `None` when the record does not have the expected/// `<hash><space><name><newline>` shape (including an empty name or input/// shorter than the hash plus separator).pub(crate) fn name_at_record_start(input: &[u8], object_hash: gix_hash::Kind) -> Option<&[u8]> {    let hex_len = object_hash.len_in_hex();    if input.get(hex_len) != Some(&b' ') {        return None;    }    let after = &input[hex_len + 1..];    let end = after.iter().position(|&b| b == b'\r' || b == b'\n')?;    if end == 0 {        return None;    }    if after[end] == b'\r' && after.get(end + 1) != Some(&b'\n') {        return None;    }    Some(&after[..end])} /// Return the byte offset of the packed-refs record containing `offset`.////// If `offset` points into a peeled object line, the returned offset points to/// the owning reference record instead.pub(crate) fn record_start_at_offset(input: &[u8], offset: usize) -> usize {    input[..offset]        .rfind(b"\n")        .and_then(|pos| {            let candidate = pos + 1;            let b = input.get(candidate)?;            if *b == b'^' {                input[..pos].rfind(b"\n").map(|pos| pos + 1)            } else {                Some(candidate)            }        })        .unwrap_or(0)} /// Return the packed-refs record containing `offset`.////// The returned slice begins at the owning reference line. It includes the/// remainder of `input`, which is enough for callers that only need to parse/// the first record.pub(crate) fn record_at_offset(input: &[u8], offset: usize) -> &[u8] {    &input[record_start_at_offset(input, offset)..]} #[cfg(test)]mod tests;