pub const HEADER_FORMAT_2: &[u8] = b"#bazaar dirstate flat format 2\n";
pub const HEADER_FORMAT_3: &[u8] = b"#bazaar dirstate flat format 3\n";
pub const BISECT_PAGE_SIZE: usize = 4096;
pub fn fields_per_entry(num_present_parents: usize) -> usize {
let tree_count = 1 + num_present_parents;
3 + 5 * tree_count + 1
}
pub fn get_ghosts_line(ghost_ids: &[&[u8]]) -> Vec<u8> {
let mut entries = Vec::new();
let l = format!("{}", ghost_ids.len());
entries.push(l.as_bytes());
entries.extend_from_slice(ghost_ids);
entries.join(&b"\0"[..])
}
pub fn get_parents_line(parent_ids: &[&[u8]]) -> Vec<u8> {
let mut entries = Vec::new();
let l = format!("{}", parent_ids.len());
entries.push(l.as_bytes());
entries.extend_from_slice(parent_ids);
entries.join(&b"\0"[..])
}
fn _crc32(bit: &[u8]) -> u32 {
let mut hasher = crc32fast::Hasher::new();
hasher.update(bit);
hasher.finalize()
}
pub fn get_output_lines(mut lines: Vec<&[u8]>) -> Vec<Vec<u8>> {
let mut output_lines = vec![HEADER_FORMAT_3];
lines.push(b"");
let inventory_text = lines.join(&b"\0\n\0"[..]).to_vec();
let crc32 = _crc32(inventory_text.as_slice());
let crc32_line = format!("crc32: {}\n", crc32).into_bytes();
output_lines.push(crc32_line.as_slice());
let num_entries = lines.len() - 3;
let num_entries_line = format!("num_entries: {}\n", num_entries).into_bytes();
output_lines.push(num_entries_line.as_slice());
output_lines.push(inventory_text.as_slice());
output_lines.into_iter().map(|l| l.to_vec()).collect()
}
#[derive(Debug, PartialEq, Eq)]
pub enum HeaderError {
BadFormatLine(Vec<u8>),
MissingCrcLine(Vec<u8>),
BadCrc(Vec<u8>),
MissingNumEntriesLine(Vec<u8>),
BadNumEntries(Vec<u8>),
BadParentsLine,
BadGhostsLine,
UnexpectedEof,
}
impl std::fmt::Display for HeaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HeaderError::BadFormatLine(line) => write!(f, "invalid header line: {:?}", line),
HeaderError::MissingCrcLine(line) => write!(f, "missing crc32 checksum: {:?}", line),
HeaderError::BadCrc(bytes) => write!(f, "invalid crc32 value: {:?}", bytes),
HeaderError::MissingNumEntriesLine(line) => {
write!(f, "missing num_entries line: {:?}", line)
}
HeaderError::BadNumEntries(bytes) => {
write!(f, "invalid num_entries value: {:?}", bytes)
}
HeaderError::BadParentsLine => write!(f, "malformed parents line"),
HeaderError::BadGhostsLine => write!(f, "malformed ghosts line"),
HeaderError::UnexpectedEof => write!(f, "unexpected end of header"),
}
}
}
impl std::error::Error for HeaderError {}
#[derive(Debug, PartialEq, Eq)]
pub struct Header {
pub crc_expected: u32,
pub num_entries: usize,
pub parents: Vec<Vec<u8>>,
pub ghosts: Vec<Vec<u8>>,
pub end_of_header: usize,
}
fn read_line(data: &[u8], pos: usize) -> Option<(&[u8], usize)> {
if pos >= data.len() {
return None;
}
let remaining = &data[pos..];
match remaining.iter().position(|&b| b == b'\n') {
Some(end) => Some((&remaining[..=end], pos + end + 1)),
None => Some((remaining, data.len())),
}
}
pub fn read_header(data: &[u8]) -> Result<Header, HeaderError> {
let mut pos = 0;
let (format_line, next) = read_line(data, pos).ok_or(HeaderError::UnexpectedEof)?;
if format_line != HEADER_FORMAT_3 {
return Err(HeaderError::BadFormatLine(format_line.to_vec()));
}
pos = next;
let (crc_line, next) = read_line(data, pos).ok_or(HeaderError::UnexpectedEof)?;
let crc_prefix: &[u8] = b"crc32: ";
if !crc_line.starts_with(crc_prefix) {
return Err(HeaderError::MissingCrcLine(crc_line.to_vec()));
}
let crc_body = crc_line[crc_prefix.len()..]
.strip_suffix(b"\n")
.unwrap_or(&crc_line[crc_prefix.len()..]);
let crc_str =
std::str::from_utf8(crc_body).map_err(|_| HeaderError::BadCrc(crc_body.to_vec()))?;
let crc_expected: u32 = crc_str
.parse()
.map_err(|_| HeaderError::BadCrc(crc_body.to_vec()))?;
pos = next;
let (num_entries_line, next) = read_line(data, pos).ok_or(HeaderError::UnexpectedEof)?;
let num_entries_prefix: &[u8] = b"num_entries: ";
if !num_entries_line.starts_with(num_entries_prefix) {
return Err(HeaderError::MissingNumEntriesLine(
num_entries_line.to_vec(),
));
}
let num_entries_body = num_entries_line[num_entries_prefix.len()..]
.strip_suffix(b"\n")
.unwrap_or(&num_entries_line[num_entries_prefix.len()..]);
let num_entries_str = std::str::from_utf8(num_entries_body)
.map_err(|_| HeaderError::BadNumEntries(num_entries_body.to_vec()))?;
let num_entries: usize = num_entries_str
.parse()
.map_err(|_| HeaderError::BadNumEntries(num_entries_body.to_vec()))?;
pos = next;
let (parents_line, next) = read_line(data, pos).ok_or(HeaderError::UnexpectedEof)?;
let parents = parse_parents_field(parents_line).ok_or(HeaderError::BadParentsLine)?;
pos = next;
let (ghosts_line, next) = read_line(data, pos).ok_or(HeaderError::UnexpectedEof)?;
let ghosts = parse_ghosts_field(ghosts_line).ok_or(HeaderError::BadGhostsLine)?;
pos = next;
Ok(Header {
crc_expected,
num_entries,
parents,
ghosts,
end_of_header: pos,
})
}
fn parse_parents_field(line: &[u8]) -> Option<Vec<Vec<u8>>> {
let parts: Vec<&[u8]> = line.split(|&b| b == 0).collect();
if parts.len() < 2 {
return None;
}
std::str::from_utf8(parts[0]).ok()?.parse::<usize>().ok()?;
Some(
parts[1..parts.len() - 1]
.iter()
.map(|s| s.to_vec())
.collect(),
)
}
fn parse_ghosts_field(line: &[u8]) -> Option<Vec<Vec<u8>>> {
let parts: Vec<&[u8]> = line.split(|&b| b == 0).collect();
if parts.len() < 3 {
return None;
}
std::str::from_utf8(parts[1]).ok()?.parse::<usize>().ok()?;
Some(
parts[2..parts.len() - 1]
.iter()
.map(|s| s.to_vec())
.collect(),
)
}