use gix_object::bstr::{BStr, ByteSlice};
use crate::{parse, store_impl::packed};
#[derive(Debug, PartialEq, Eq)]
enum Peeled {
Unspecified,
Partial,
Fully,
}
#[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,
}
}
}
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)
}
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 })
}
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 })
}
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])
}
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)
}
pub(crate) fn record_at_offset(input: &[u8], offset: usize) -> &[u8] {
&input[record_start_at_offset(input, offset)..]
}
#[cfg(test)]
mod tests;