#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KnitError {
MissingOrigin(Vec<u8>),
BadDeltaHeader(Vec<u8>),
TruncatedDelta,
Gzip(String),
EmptyRecord,
HeaderFields(Vec<u8>),
HeaderCount(Vec<u8>),
LineCount { declared: usize, actual: usize },
BadEndMarker { expected: Vec<u8>, actual: Vec<u8> },
MissingTrailingNewline,
NetworkMissingKeyTerminator,
NetworkMissingParentsTerminator,
NetworkMissingNoEolByte,
BadIndexValue(Vec<u8>),
TooManyCompressionParents(usize),
UnexpectedVersion { wanted: Vec<u8>, got: Vec<u8> },
}
impl std::fmt::Display for KnitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KnitError::MissingOrigin(l) => {
write!(f, "annotated knit line missing origin: {:?}", l)
}
KnitError::BadDeltaHeader(h) => write!(f, "bad delta header: {:?}", h),
KnitError::TruncatedDelta => write!(f, "delta truncated: too few lines"),
KnitError::Gzip(msg) => write!(f, "corrupt compressed record: {}", msg),
KnitError::EmptyRecord => write!(f, "empty knit record"),
KnitError::HeaderFields(h) => {
write!(f, "unexpected number of elements in record header: {:?}", h)
}
KnitError::HeaderCount(h) => {
write!(f, "record header line count is not an integer: {:?}", h)
}
KnitError::LineCount { declared, actual } => {
write!(
f,
"incorrect number of lines {} != {} in record",
actual, declared
)
}
KnitError::BadEndMarker { expected, actual } => write!(
f,
"unexpected version end line {:?}, wanted {:?}",
actual, expected
),
KnitError::MissingTrailingNewline => {
write!(f, "corrupt lines value: last line missing trailing newline")
}
KnitError::NetworkMissingKeyTerminator => {
write!(f, "knit network record key missing newline terminator")
}
KnitError::NetworkMissingParentsTerminator => {
write!(f, "knit network record parents missing newline terminator")
}
KnitError::NetworkMissingNoEolByte => {
write!(f, "knit network record missing noeol byte")
}
KnitError::BadIndexValue(v) => {
write!(f, "bad knit index value: {:?}", v)
}
KnitError::TooManyCompressionParents(n) => {
write!(f, "Too many compression parents: {}", n)
}
KnitError::UnexpectedVersion { wanted, got } => {
write!(f, "unexpected version, wanted {:?}, got {:?}", wanted, got)
}
}
}
}
impl std::error::Error for KnitError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeltaHunk<T> {
pub start: usize,
pub end: usize,
pub count: usize,
pub lines: Vec<T>,
}
pub type AnnotatedLine = (Vec<u8>, Vec<u8>);
pub fn parse_fulltext(lines: &[&[u8]]) -> Result<Vec<AnnotatedLine>, KnitError> {
lines.iter().map(|l| split_annotated(l)).collect()
}
pub fn lower_fulltext(content: &[(Vec<u8>, Vec<u8>)]) -> Vec<Vec<u8>> {
content
.iter()
.map(|(origin, text)| {
let mut out = Vec::with_capacity(origin.len() + 1 + text.len());
out.extend_from_slice(origin);
out.push(b' ');
out.extend_from_slice(text);
out
})
.collect()
}
pub fn parse_line_delta_annotated(
lines: &[&[u8]],
) -> Result<Vec<DeltaHunk<AnnotatedLine>>, KnitError> {
parse_line_delta_inner(lines, true).map(|hunks| {
hunks
.into_iter()
.map(|h| DeltaHunk {
start: h.start,
end: h.end,
count: h.count,
lines: h
.lines
.into_iter()
.map(|line| match line {
ParsedLine::Annotated(o, t) => (o, t),
ParsedLine::Plain(_) => unreachable!(),
})
.collect(),
})
.collect()
})
}
pub fn parse_line_delta_plain(lines: &[&[u8]]) -> Result<Vec<DeltaHunk<Vec<u8>>>, KnitError> {
parse_line_delta_inner(lines, false).map(|hunks| {
hunks
.into_iter()
.map(|h| DeltaHunk {
start: h.start,
end: h.end,
count: h.count,
lines: h
.lines
.into_iter()
.map(|line| match line {
ParsedLine::Plain(t) => t,
ParsedLine::Annotated(_, t) => t,
})
.collect(),
})
.collect()
})
}
pub fn lower_line_delta_annotated(delta: &[DeltaHunk<AnnotatedLine>]) -> Vec<Vec<u8>> {
let mut out = Vec::new();
for hunk in delta {
out.push(format!("{},{},{}\n", hunk.start, hunk.end, hunk.count).into_bytes());
for (origin, text) in &hunk.lines {
let mut line = Vec::with_capacity(origin.len() + 1 + text.len());
line.extend_from_slice(origin);
line.push(b' ');
line.extend_from_slice(text);
out.push(line);
}
}
out
}
pub fn parse_line_delta_raw(lines: &[&[u8]]) -> Result<Vec<DeltaHunk<Vec<u8>>>, KnitError> {
let mut out = Vec::new();
let mut i = 0;
while i < lines.len() {
let (start, end, count) = parse_delta_header(lines[i])?;
i += 1;
if i + count > lines.len() {
return Err(KnitError::TruncatedDelta);
}
let hunk_lines: Vec<Vec<u8>> = lines[i..i + count].iter().map(|l| l.to_vec()).collect();
i += count;
out.push(DeltaHunk {
start,
end,
count,
lines: hunk_lines,
});
}
Ok(out)
}
pub fn lower_line_delta_raw(delta: &[DeltaHunk<Vec<u8>>]) -> Vec<Vec<u8>> {
let mut out = Vec::new();
for hunk in delta {
out.push(format!("{},{},{}\n", hunk.start, hunk.end, hunk.count).into_bytes());
for line in &hunk.lines {
out.push(line.clone());
}
}
out
}
pub fn get_line_delta_blocks(
delta: &[(usize, usize, usize)],
source: &[&[u8]],
target: &[&[u8]],
) -> Vec<(usize, usize, usize)> {
let target_len = target.len();
let mut out = Vec::new();
let mut s_pos = 0usize;
let mut t_pos = 0usize;
for &(s_begin, s_end, t_len) in delta {
let true_n = s_begin - s_pos;
let mut n = true_n;
if n > 0 {
if source[s_pos + n - 1] != target[t_pos + n - 1] {
n -= 1;
}
if n > 0 {
out.push((s_pos, t_pos, n));
}
}
t_pos += t_len + true_n;
s_pos = s_end;
}
let mut n = target_len - t_pos;
if n > 0 {
if source[s_pos + n - 1] != target[t_pos + n - 1] {
n -= 1;
}
if n > 0 {
out.push((s_pos, t_pos, n));
}
}
out.push((s_pos + (target_len - t_pos), target_len, 0));
out
}
pub trait KnitContent {
type DeltaLine: Clone;
fn should_strip_eol(&self) -> bool;
fn set_should_strip_eol(&mut self, strip: bool);
fn apply_delta(&mut self, delta: &[DeltaHunk<Self::DeltaLine>], new_version_id: &[u8]);
fn text(&self) -> Vec<Vec<u8>>;
fn annotate(&self) -> Vec<AnnotatedLine>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnnotatedKnitContent {
pub lines: Vec<AnnotatedLine>,
should_strip_eol: bool,
}
impl AnnotatedKnitContent {
pub fn new(lines: Vec<AnnotatedLine>) -> Self {
Self {
lines,
should_strip_eol: false,
}
}
}
impl KnitContent for AnnotatedKnitContent {
type DeltaLine = AnnotatedLine;
fn should_strip_eol(&self) -> bool {
self.should_strip_eol
}
fn set_should_strip_eol(&mut self, strip: bool) {
self.should_strip_eol = strip;
}
fn apply_delta(&mut self, delta: &[DeltaHunk<AnnotatedLine>], _new_version_id: &[u8]) {
let mut offset: isize = 0;
for hunk in delta {
let start = (offset + hunk.start as isize) as usize;
let end = (offset + hunk.end as isize) as usize;
self.lines.splice(start..end, hunk.lines.iter().cloned());
offset += hunk.start as isize - hunk.end as isize + hunk.count as isize;
}
}
fn text(&self) -> Vec<Vec<u8>> {
let mut out: Vec<Vec<u8>> = self.lines.iter().map(|(_, t)| t.clone()).collect();
if self.should_strip_eol {
if let Some(last) = out.last_mut() {
if last.ends_with(b"\n") {
last.pop();
}
}
}
out
}
fn annotate(&self) -> Vec<AnnotatedLine> {
let mut out = self.lines.clone();
if self.should_strip_eol {
if let Some((_, last)) = out.last_mut() {
if last.ends_with(b"\n") {
last.pop();
}
}
}
out
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlainKnitContent {
pub lines: Vec<Vec<u8>>,
pub version_id: Vec<u8>,
should_strip_eol: bool,
}
impl PlainKnitContent {
pub fn new(lines: Vec<Vec<u8>>, version_id: Vec<u8>) -> Self {
Self {
lines,
version_id,
should_strip_eol: false,
}
}
}
impl KnitContent for PlainKnitContent {
type DeltaLine = Vec<u8>;
fn should_strip_eol(&self) -> bool {
self.should_strip_eol
}
fn set_should_strip_eol(&mut self, strip: bool) {
self.should_strip_eol = strip;
}
fn apply_delta(&mut self, delta: &[DeltaHunk<Vec<u8>>], new_version_id: &[u8]) {
let mut offset: isize = 0;
for hunk in delta {
let start = (offset + hunk.start as isize) as usize;
let end = (offset + hunk.end as isize) as usize;
self.lines.splice(start..end, hunk.lines.iter().cloned());
offset += hunk.start as isize - hunk.end as isize + hunk.count as isize;
}
self.version_id = new_version_id.to_vec();
}
fn text(&self) -> Vec<Vec<u8>> {
let mut out = self.lines.clone();
if self.should_strip_eol {
if let Some(last) = out.last_mut() {
if last.ends_with(b"\n") {
last.pop();
}
}
}
out
}
fn annotate(&self) -> Vec<AnnotatedLine> {
self.lines
.iter()
.map(|l| (self.version_id.clone(), l.clone()))
.collect()
}
}
pub trait KnitFactory {
type Content: KnitContent + Clone;
fn annotated(&self) -> bool;
fn parse_fulltext_content(
&self,
lines: &[&[u8]],
version_id: &[u8],
) -> Result<Self::Content, KnitError>;
fn parse_line_delta(
&self,
lines: &[&[u8]],
) -> Result<Vec<DeltaHunk<<Self::Content as KnitContent>::DeltaLine>>, KnitError>;
fn parse_record(
&self,
version_id: &[u8],
body_lines: &[&[u8]],
method: KnitMethod,
noeol: bool,
base_content: Option<&Self::Content>,
) -> Result<Self::Content, KnitError> {
let mut content = match method {
KnitMethod::Fulltext => self.parse_fulltext_content(body_lines, version_id)?,
KnitMethod::LineDelta => {
let base = base_content.ok_or_else(|| {
KnitError::BadIndexValue(b"line-delta record requires base content".to_vec())
})?;
let mut content = base.clone();
let delta = self.parse_line_delta(body_lines)?;
content.apply_delta(&delta, version_id);
content
}
};
content.set_should_strip_eol(noeol);
Ok(content)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct KnitAnnotateFactory;
impl KnitFactory for KnitAnnotateFactory {
type Content = AnnotatedKnitContent;
fn annotated(&self) -> bool {
true
}
fn parse_fulltext_content(
&self,
lines: &[&[u8]],
_version_id: &[u8],
) -> Result<Self::Content, KnitError> {
let pairs = parse_fulltext(lines)?;
Ok(AnnotatedKnitContent::new(pairs))
}
fn parse_line_delta(
&self,
lines: &[&[u8]],
) -> Result<Vec<DeltaHunk<AnnotatedLine>>, KnitError> {
parse_line_delta_annotated(lines)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct KnitPlainFactory;
impl KnitFactory for KnitPlainFactory {
type Content = PlainKnitContent;
fn annotated(&self) -> bool {
false
}
fn parse_fulltext_content(
&self,
lines: &[&[u8]],
version_id: &[u8],
) -> Result<Self::Content, KnitError> {
let lines: Vec<Vec<u8>> = lines.iter().map(|l| l.to_vec()).collect();
Ok(PlainKnitContent::new(lines, version_id.to_vec()))
}
fn parse_line_delta(&self, lines: &[&[u8]]) -> Result<Vec<DeltaHunk<Vec<u8>>>, KnitError> {
parse_line_delta_raw(lines)
}
}
enum ParsedLine {
Annotated(Vec<u8>, Vec<u8>),
Plain(Vec<u8>),
}
fn split_annotated(line: &[u8]) -> Result<(Vec<u8>, Vec<u8>), KnitError> {
let sp = line
.iter()
.position(|&b| b == b' ')
.ok_or_else(|| KnitError::MissingOrigin(line.to_vec()))?;
Ok((line[..sp].to_vec(), line[sp + 1..].to_vec()))
}
fn parse_line_delta_inner(
lines: &[&[u8]],
annotated: bool,
) -> Result<Vec<DeltaHunk<ParsedLine>>, KnitError> {
let mut out = Vec::new();
let mut i = 0;
while i < lines.len() {
let header = lines[i];
i += 1;
let (start, end, count) = parse_delta_header(header)?;
if i + count > lines.len() {
return Err(KnitError::TruncatedDelta);
}
let mut hunk_lines = Vec::with_capacity(count);
for raw in &lines[i..i + count] {
let (origin, text) = split_annotated(raw)?;
hunk_lines.push(if annotated {
ParsedLine::Annotated(origin, text)
} else {
ParsedLine::Plain(text)
});
}
i += count;
out.push(DeltaHunk {
start,
end,
count,
lines: hunk_lines,
});
}
Ok(out)
}
fn parse_delta_header(line: &[u8]) -> Result<(usize, usize, usize), KnitError> {
let trimmed = line.strip_suffix(b"\n").unwrap_or(line);
let mut parts = trimmed.split(|&b| b == b',');
let mut next = || -> Result<usize, KnitError> {
let part = parts
.next()
.ok_or_else(|| KnitError::BadDeltaHeader(line.to_vec()))?;
std::str::from_utf8(part)
.ok()
.and_then(|s| s.parse().ok())
.ok_or_else(|| KnitError::BadDeltaHeader(line.to_vec()))
};
let start = next()?;
let end = next()?;
let count = next()?;
if parts.next().is_some() {
return Err(KnitError::BadDeltaHeader(line.to_vec()));
}
Ok((start, end, count))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NetworkRecordHeader<'a> {
pub key: Vec<&'a [u8]>,
pub parents: Option<Vec<Vec<&'a [u8]>>>,
pub noeol: bool,
pub raw_record: &'a [u8],
}
pub fn parse_network_record_header(
bytes: &[u8],
start: usize,
) -> Result<NetworkRecordHeader<'_>, KnitError> {
let key_end = bytes[start..]
.iter()
.position(|&b| b == b'\n')
.map(|i| start + i)
.ok_or(KnitError::NetworkMissingKeyTerminator)?;
let key: Vec<&[u8]> = bytes[start..key_end].split(|&b| b == b'\x00').collect();
let parents_start = key_end + 1;
let parents_end = bytes[parents_start..]
.iter()
.position(|&b| b == b'\n')
.map(|i| parents_start + i)
.ok_or(KnitError::NetworkMissingParentsTerminator)?;
let parents_line = &bytes[parents_start..parents_end];
let parents = if parents_line == b"None:" {
None
} else {
Some(
parents_line
.split(|&b| b == b'\t')
.filter(|seg| !seg.is_empty())
.map(|seg| seg.split(|&b| b == b'\x00').collect::<Vec<&[u8]>>())
.collect(),
)
};
let noeol_pos = parents_end + 1;
if noeol_pos >= bytes.len() {
return Err(KnitError::NetworkMissingNoEolByte);
}
let noeol = bytes[noeol_pos] == b'N';
let raw_record = &bytes[noeol_pos + 1..];
Ok(NetworkRecordHeader {
key,
parents,
noeol,
raw_record,
})
}
pub const NO_PARENTS: Option<&[&[&[u8]]]> = None;
fn write_joined_key<Seg: AsRef<[u8]>>(out: &mut Vec<u8>, key: &[Seg]) {
for (i, segment) in key.iter().enumerate() {
if i > 0 {
out.push(b'\x00');
}
out.extend_from_slice(segment.as_ref());
}
}
pub fn build_network_record<Seg, PK>(
storage_kind: &[u8],
key: &[Seg],
parents: Option<&[PK]>,
noeol: bool,
raw_record: &[u8],
) -> Vec<u8>
where
Seg: AsRef<[u8]>,
PK: AsRef<[Seg]>,
{
let mut out = Vec::with_capacity(storage_kind.len() + raw_record.len() + 32);
out.extend_from_slice(storage_kind);
out.push(b'\n');
write_joined_key(&mut out, key);
out.push(b'\n');
match parents {
None => out.extend_from_slice(b"None:"),
Some(list) => {
for (i, parent) in list.iter().enumerate() {
if i > 0 {
out.push(b'\t');
}
write_joined_key(&mut out, parent.as_ref());
}
}
}
out.push(b'\n');
out.push(if noeol { b'N' } else { b' ' });
out.extend_from_slice(raw_record);
out
}
pub fn dictionary_compress_suffixes<S>(
suffixes: &[S],
lookup: &std::collections::HashMap<&[u8], u64>,
) -> Vec<u8>
where
S: AsRef<[u8]>,
{
if suffixes.is_empty() {
return Vec::new();
}
let mut out = Vec::new();
for (i, suffix) in suffixes.iter().enumerate() {
if i > 0 {
out.push(b' ');
}
let s = suffix.as_ref();
match lookup.get(s) {
Some(pos) => {
use std::io::Write;
write!(out, "{}", pos).unwrap();
}
None => {
out.push(b'.');
out.extend_from_slice(s);
}
}
}
out
}
#[allow(clippy::type_complexity)]
pub fn split_keys_by_prefix<'a, K, Seg>(
keys: &'a [K],
) -> (Vec<(&'a [u8], Vec<&'a K>)>, Vec<&'a [u8]>)
where
K: AsRef<[Seg]> + 'a,
Seg: AsRef<[u8]> + 'a,
{
use std::collections::HashMap;
const EMPTY: &[u8] = b"";
let mut buckets: Vec<(&'a [u8], Vec<&'a K>)> = Vec::new();
let mut index: HashMap<&'a [u8], usize> = HashMap::new();
let mut prefix_order: Vec<&'a [u8]> = Vec::new();
for key in keys {
let segments: &'a [Seg] = key.as_ref();
let prefix: &'a [u8] = if segments.len() == 1 {
EMPTY
} else {
segments[0].as_ref()
};
match index.get(prefix) {
Some(&i) => buckets[i].1.push(key),
None => {
index.insert(prefix, buckets.len());
prefix_order.push(prefix);
buckets.push((prefix, vec![key]));
}
}
}
(buckets, prefix_order)
}
pub struct KnitDeltaClosureRecord<'a, Seg: AsRef<[u8]>> {
pub key: &'a [Seg],
pub parents: Option<&'a [&'a [Seg]]>,
pub method: &'a [u8],
pub noeol: bool,
pub next: Option<&'a [Seg]>,
pub record_bytes: &'a [u8],
}
pub fn build_knit_delta_closure_wire<EK, Seg>(
annotated: bool,
emit_keys: &[EK],
records: &[KnitDeltaClosureRecord<'_, Seg>],
) -> Vec<u8>
where
EK: AsRef<[Seg]>,
Seg: AsRef<[u8]>,
{
let body_estimate: usize = records.iter().map(|r| r.record_bytes.len() + 64).sum();
let mut out = Vec::with_capacity(64 + body_estimate);
out.extend_from_slice(b"knit-delta-closure\n");
if annotated {
out.extend_from_slice(b"annotated");
}
out.push(b'\n');
for (i, key) in emit_keys.iter().enumerate() {
if i > 0 {
out.push(b'\t');
}
write_joined_key(&mut out, key.as_ref());
}
out.push(b'\n');
for rec in records {
write_joined_key(&mut out, rec.key);
out.push(b'\n');
match rec.parents {
None => out.extend_from_slice(b"None:"),
Some(list) => {
for (i, parent) in list.iter().enumerate() {
if i > 0 {
out.push(b'\t');
}
write_joined_key(&mut out, parent);
}
}
}
out.push(b'\n');
out.extend_from_slice(rec.method);
out.push(b'\n');
out.push(if rec.noeol { b'T' } else { b'F' });
out.push(b'\n');
if let Some(next) = rec.next {
write_joined_key(&mut out, next);
}
out.push(b'\n');
out.extend_from_slice(rec.record_bytes.len().to_string().as_bytes());
out.push(b'\n');
out.extend_from_slice(rec.record_bytes);
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecordHeader {
pub method: Vec<u8>,
pub version_id: Vec<u8>,
pub count: usize,
pub digest: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecordHeaderRef<'a> {
pub method: &'a [u8],
pub version_id: &'a [u8],
pub count: usize,
pub digest: &'a [u8],
}
impl RecordHeaderRef<'_> {
pub fn to_owned(&self) -> RecordHeader {
RecordHeader {
method: self.method.to_vec(),
version_id: self.version_id.to_vec(),
count: self.count,
digest: self.digest.to_vec(),
}
}
}
pub fn parse_header_line(line: &[u8]) -> Result<RecordHeaderRef<'_>, KnitError> {
let trimmed = line.strip_suffix(b"\n").unwrap_or(line);
let fields: Vec<&[u8]> = trimmed.split(|&b| b == b' ').collect();
if fields.len() != 4 {
return Err(KnitError::HeaderFields(line.to_vec()));
}
let count: usize = std::str::from_utf8(fields[2])
.ok()
.and_then(|s| s.parse().ok())
.ok_or_else(|| KnitError::HeaderCount(line.to_vec()))?;
Ok(RecordHeaderRef {
method: fields[0],
version_id: fields[1],
count,
digest: fields[3],
})
}
fn split_readlines(data: &[u8]) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut start = 0;
for (i, &b) in data.iter().enumerate() {
if b == b'\n' {
out.push(data[start..=i].to_vec());
start = i + 1;
}
}
if start < data.len() {
out.push(data[start..].to_vec());
}
out
}
pub fn decode_record_gz(data: &[u8]) -> Result<Vec<u8>, KnitError> {
use flate2::read::GzDecoder;
use std::io::Read;
let mut decoder = GzDecoder::new(data);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| KnitError::Gzip(e.to_string()))?;
Ok(decompressed)
}
pub fn readlines(data: &[u8]) -> Vec<&[u8]> {
ReadLines::new(data).collect()
}
#[derive(Debug, Clone)]
pub struct ReadLines<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> ReadLines<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
}
impl<'a> Iterator for ReadLines<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.data.len() {
return None;
}
let start = self.pos;
match self.data[start..].iter().position(|&b| b == b'\n') {
Some(off) => {
let end = start + off + 1;
self.pos = end;
Some(&self.data[start..end])
}
None => {
self.pos = self.data.len();
Some(&self.data[start..])
}
}
}
}
pub fn parse_record_body_unchecked(
decompressed: &[u8],
) -> Result<(RecordHeaderRef<'_>, Vec<&[u8]>), KnitError> {
let mut lines = readlines(decompressed);
if lines.is_empty() {
return Err(KnitError::EmptyRecord);
}
let header_line = lines.remove(0);
let header = parse_header_line(header_line)?;
if lines.is_empty() {
return Err(KnitError::LineCount {
declared: header.count,
actual: 0,
});
}
let last_line = lines.pop().unwrap();
if lines.len() != header.count {
return Err(KnitError::LineCount {
declared: header.count,
actual: lines.len(),
});
}
let mut expected_end = b"end ".to_vec();
expected_end.extend_from_slice(header.version_id);
expected_end.push(b'\n');
if last_line != expected_end.as_slice() {
return Err(KnitError::BadEndMarker {
expected: expected_end,
actual: last_line.to_vec(),
});
}
Ok((header, lines))
}
pub fn parse_record_unchecked(data: &[u8]) -> Result<(RecordHeader, Vec<Vec<u8>>), KnitError> {
let decompressed = decode_record_gz(data)?;
let mut lines = split_readlines(&decompressed);
if lines.is_empty() {
return Err(KnitError::EmptyRecord);
}
let header_line = lines.remove(0);
let header = parse_header_line(&header_line)?.to_owned();
if lines.is_empty() {
return Err(KnitError::LineCount {
declared: header.count,
actual: 0,
});
}
let last_line = lines.pop().unwrap();
if lines.len() != header.count {
return Err(KnitError::LineCount {
declared: header.count,
actual: lines.len(),
});
}
let mut expected_end = b"end ".to_vec();
expected_end.extend_from_slice(&header.version_id);
expected_end.push(b'\n');
if last_line != expected_end {
return Err(KnitError::BadEndMarker {
expected: expected_end,
actual: last_line,
});
}
Ok((header, lines))
}
pub fn parse_record(
expected_version: &[u8],
data: &[u8],
) -> Result<(Vec<Vec<u8>>, Vec<u8>), KnitError> {
let (header, body) = parse_record_unchecked(data)?;
if header.version_id != expected_version {
return Err(KnitError::UnexpectedVersion {
wanted: expected_version.to_vec(),
got: header.version_id,
});
}
Ok((body, header.digest))
}
pub fn parse_record_header_only(data: &[u8]) -> Result<RecordHeader, KnitError> {
use flate2::read::GzDecoder;
use std::io::Read;
let mut decoder = GzDecoder::new(data);
let mut header_buf = Vec::with_capacity(64);
let mut byte = [0u8; 1];
loop {
match decoder
.read(&mut byte)
.map_err(|e| KnitError::Gzip(e.to_string()))?
{
0 => break,
_ => {
header_buf.push(byte[0]);
if byte[0] == b'\n' {
break;
}
}
}
}
if header_buf.is_empty() {
return Err(KnitError::EmptyRecord);
}
Ok(parse_header_line(&header_buf)?.to_owned())
}
pub fn record_to_data<P>(
version_id: &[u8],
digest: &[u8],
line_count: usize,
payload: &[P],
has_trailing_newline: bool,
) -> Result<(usize, Vec<Vec<u8>>), KnitError>
where
P: AsRef<[u8]>,
{
if !has_trailing_newline {
return Err(KnitError::MissingTrailingNewline);
}
let mut header = Vec::with_capacity(version_id.len() + digest.len() + 16);
header.extend_from_slice(b"version ");
header.extend_from_slice(version_id);
header.extend_from_slice(format!(" {} ", line_count).as_bytes());
header.extend_from_slice(digest);
header.push(b'\n');
let mut end = Vec::with_capacity(version_id.len() + 5);
end.extend_from_slice(b"end ");
end.extend_from_slice(version_id);
end.push(b'\n');
let mut chunks: Vec<&[u8]> = Vec::with_capacity(payload.len() + 2);
chunks.push(&header);
for p in payload {
chunks.push(p.as_ref());
}
chunks.push(&end);
let compressed = crate::tuned_gzip::chunks_to_gzip(chunks.into_iter());
let total: usize = compressed.iter().map(|c| c.len()).sum();
Ok((total, compressed))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KnitMethod {
Fulltext,
LineDelta,
}
impl KnitMethod {
pub fn as_str(self) -> &'static str {
match self {
KnitMethod::Fulltext => "fulltext",
KnitMethod::LineDelta => "line-delta",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KnitIndexValue {
pub noeol: bool,
pub pos: u64,
pub size: u64,
}
pub fn parse_knit_index_value(value: &[u8]) -> Result<KnitIndexValue, KnitError> {
if value.is_empty() {
return Err(KnitError::BadIndexValue(value.to_vec()));
}
let noeol = value[0] == b'N';
let trimmed = &value[1..];
let mut parts = trimmed.splitn(2, |&b| b == b' ');
let pos_bytes = parts
.next()
.ok_or_else(|| KnitError::BadIndexValue(value.to_vec()))?;
let size_bytes = parts
.next()
.ok_or_else(|| KnitError::BadIndexValue(value.to_vec()))?;
let pos: u64 = std::str::from_utf8(pos_bytes)
.ok()
.and_then(|s| s.parse().ok())
.ok_or_else(|| KnitError::BadIndexValue(value.to_vec()))?;
let size: u64 = std::str::from_utf8(size_bytes)
.ok()
.and_then(|s| s.parse().ok())
.ok_or_else(|| KnitError::BadIndexValue(value.to_vec()))?;
Ok(KnitIndexValue { noeol, pos, size })
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KnitBuildDetails {
pub pos: u64,
pub size: u64,
pub noeol: bool,
pub method: KnitMethod,
pub compression_parent: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClosureBatch<K, P>
where
K: Eq + std::hash::Hash + Clone,
{
pub present: std::collections::HashMap<K, (Option<K>, P)>,
pub missing: std::collections::HashSet<K>,
}
#[allow(clippy::type_complexity)]
pub fn walk_compression_closure<K, P, F>(
initial_keys: impl IntoIterator<Item = K>,
allow_missing: bool,
mut lookup_batch: F,
) -> Result<std::collections::HashMap<K, P>, std::collections::HashSet<K>>
where
K: Eq + std::hash::Hash + Clone,
F: FnMut(&[K]) -> ClosureBatch<K, P>,
{
use std::collections::HashMap;
let mut result: HashMap<K, P> = HashMap::new();
let mut pending: Vec<K> = initial_keys.into_iter().collect();
while !pending.is_empty() {
let batch = lookup_batch(&pending);
if !batch.missing.is_empty() && !allow_missing {
return Err(batch.missing);
}
let mut next: Vec<K> = Vec::new();
for (key, (compression_parent, payload)) in batch.present {
if let Some(parent) = compression_parent.as_ref() {
if !result.contains_key(parent) && !next.contains(parent) {
next.push(parent.clone());
}
}
result.insert(key, payload);
}
pending = next;
}
Ok(result)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeltaDecision {
UseDelta,
FulltextSmaller,
ChainTooLong,
MissingParent,
}
impl DeltaDecision {
pub fn should_use_delta(self) -> bool {
matches!(self, DeltaDecision::UseDelta)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChainStep<K> {
pub size: u64,
pub compression_parent: Option<K>,
}
pub fn should_use_delta<K, F>(initial_parent: K, max_chain: usize, mut get_step: F) -> DeltaDecision
where
F: FnMut(&K) -> Option<ChainStep<K>>,
{
let mut delta_size: u64 = 0;
let mut current = initial_parent;
for _ in 0..max_chain {
let step = match get_step(¤t) {
Some(s) => s,
None => return DeltaDecision::MissingParent,
};
match step.compression_parent {
None => {
return if step.size > delta_size {
DeltaDecision::UseDelta
} else {
DeltaDecision::FulltextSmaller
};
}
Some(next) => {
delta_size += step.size;
current = next;
}
}
}
DeltaDecision::ChainTooLong
}
pub fn decode_kndx_options<O: AsRef<[u8]>>(options: &[O]) -> Result<(KnitMethod, bool), KnitError> {
let mut method: Option<KnitMethod> = None;
let mut noeol = false;
for opt in options {
let o = opt.as_ref();
if o == b"fulltext" {
method = Some(KnitMethod::Fulltext);
} else if o == b"line-delta" {
method = Some(KnitMethod::LineDelta);
} else if o == b"no-eol" {
noeol = true;
}
}
let method = method.ok_or_else(|| {
KnitError::BadIndexValue(
options
.iter()
.flat_map(|o| {
let mut v = o.as_ref().to_vec();
v.push(b',');
v
})
.collect(),
)
})?;
Ok((method, noeol))
}
pub fn decode_knit_build_details(
value: &[u8],
has_deltas: bool,
compression_parent_count: usize,
) -> Result<KnitBuildDetails, KnitError> {
let parsed = parse_knit_index_value(value)?;
let compression_parent = if has_deltas {
match compression_parent_count {
0 => None,
1 => Some(0),
n => return Err(KnitError::TooManyCompressionParents(n)),
}
} else {
None
};
let method = if compression_parent.is_some() {
KnitMethod::LineDelta
} else {
KnitMethod::Fulltext
};
Ok(KnitBuildDetails {
pos: parsed.pos,
size: parsed.size,
noeol: parsed.noeol,
method,
compression_parent,
})
}
pub fn extract_annotated_fulltext_to_plain_lines(
raw_record: &[u8],
noeol: bool,
) -> Result<Vec<Vec<u8>>, KnitError> {
let decompressed = decode_record_gz(raw_record)?;
let (_header, body_lines) = parse_record_body_unchecked(&decompressed)?;
let annotated: Vec<AnnotatedLine> = parse_fulltext(&body_lines)?;
let mut lines: Vec<Vec<u8>> = annotated.into_iter().map(|(_, text)| text).collect();
if noeol {
if let Some(last) = lines.last_mut() {
if last.ends_with(b"\n") {
last.pop();
}
}
}
Ok(lines)
}
pub fn extract_plain_fulltext_lines(
raw_record: &[u8],
noeol: bool,
) -> Result<Vec<Vec<u8>>, KnitError> {
let decompressed = decode_record_gz(raw_record)?;
let (_header, body_lines) = parse_record_body_unchecked(&decompressed)?;
let mut lines: Vec<Vec<u8>> = body_lines.iter().map(|l| l.to_vec()).collect();
if noeol {
if let Some(last) = lines.last_mut() {
if last.ends_with(b"\n") {
last.pop();
}
}
}
Ok(lines)
}
pub fn recompress_annotated_to_unannotated_fulltext(
raw_record: &[u8],
) -> Result<Vec<u8>, KnitError> {
let decompressed = decode_record_gz(raw_record)?;
let (header, body_lines) = parse_record_body_unchecked(&decompressed)?;
let annotated: Vec<AnnotatedLine> = parse_fulltext(&body_lines)?;
let plain_lines: Vec<Vec<u8>> = annotated.into_iter().map(|(_, text)| text).collect();
let has_trailing_newline = plain_lines.last().is_none_or(|l| l.ends_with(b"\n"));
let (_, chunks) = record_to_data(
header.version_id,
header.digest,
plain_lines.len(),
&plain_lines,
has_trailing_newline,
)?;
Ok(chunks.into_iter().flatten().collect())
}
pub fn recompress_annotated_to_unannotated_delta(raw_record: &[u8]) -> Result<Vec<u8>, KnitError> {
let decompressed = decode_record_gz(raw_record)?;
let (header, body_lines) = parse_record_body_unchecked(&decompressed)?;
let plain_delta = parse_line_delta_plain(&body_lines)?;
let plain_bytes = lower_line_delta_raw(&plain_delta);
let has_trailing_newline = plain_bytes.last().is_none_or(|l| l.ends_with(b"\n"));
let (_, chunks) = record_to_data(
header.version_id,
header.digest,
plain_bytes.len(),
&plain_bytes,
has_trailing_newline,
)?;
Ok(chunks.into_iter().flatten().collect())
}
pub type KnitKey = Vec<Vec<u8>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KnitRecordDetails {
pub method: KnitMethod,
pub noeol: bool,
pub index_memo: KnitIndexMemo,
pub compression_parent: Option<KnitKey>,
pub parents: Vec<KnitKey>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KnitIndexMemo {
pub path: String,
pub offset: u64,
pub length: usize,
}
pub trait KnitIndex {
fn get_build_details(
&self,
keys: &[KnitKey],
) -> Result<std::collections::HashMap<KnitKey, KnitRecordDetails>, KnitError>;
}
pub trait KnitAccess {
fn get_raw_record(&self, memo: &KnitIndexMemo) -> Result<Vec<u8>, KnitError>;
}
pub fn get_text<I, A, F>(
index: &I,
access: &A,
factory: &F,
key: &KnitKey,
) -> Result<Vec<u8>, KnitError>
where
I: KnitIndex,
A: KnitAccess,
F: KnitFactory,
{
let content = get_content(index, access, factory, key)?;
let mut out = Vec::new();
for line in content.text() {
out.extend_from_slice(&line);
}
Ok(out)
}
pub fn get_content<I, A, F>(
index: &I,
access: &A,
factory: &F,
key: &KnitKey,
) -> Result<F::Content, KnitError>
where
I: KnitIndex,
A: KnitAccess,
F: KnitFactory,
{
let chain = walk_compression_closure::<KnitKey, KnitRecordDetails, _>(
std::iter::once(key.clone()),
false,
|batch| {
let lookup = match index.get_build_details(batch) {
Ok(m) => m,
Err(_) => {
return ClosureBatch {
present: Default::default(),
missing: batch.iter().cloned().collect(),
};
}
};
let mut present = std::collections::HashMap::new();
let mut missing = std::collections::HashSet::new();
for k in batch {
match lookup.get(k) {
Some(details) => {
present.insert(
k.clone(),
(details.compression_parent.clone(), details.clone()),
);
}
None => {
missing.insert(k.clone());
}
}
}
ClosureBatch { present, missing }
},
)
.map_err(|missing| {
let one = missing
.into_iter()
.next()
.map(|k| {
let last = k.last().cloned().unwrap_or_default();
last
})
.unwrap_or_default();
KnitError::BadIndexValue(one)
})?;
let mut order: Vec<KnitKey> = Vec::new();
let mut cursor: Option<KnitKey> = Some(key.clone());
while let Some(k) = cursor {
let details = chain.get(&k).ok_or_else(|| {
KnitError::BadIndexValue(b"chain walk produced a key without details".to_vec())
})?;
cursor = details.compression_parent.clone();
order.push(k);
}
order.reverse();
let mut content: Option<F::Content> = None;
for chain_key in order {
let details = chain.get(&chain_key).ok_or_else(|| {
KnitError::BadIndexValue(b"chain walk produced a key without details".to_vec())
})?;
let raw = access.get_raw_record(&details.index_memo)?;
let decompressed = decode_record_gz(&raw)?;
let (_, body_lines) = parse_record_body_unchecked(&decompressed)?;
let next = factory.parse_record(
chain_key.last().map(|s| s.as_slice()).unwrap_or(&[]),
&body_lines,
details.method,
details.noeol,
content.as_ref(),
)?;
content = Some(next);
}
content.ok_or_else(|| KnitError::BadIndexValue(b"empty compression chain for key".to_vec()))
}
pub fn get_sha1s<I, A>(
index: &I,
access: &A,
keys: &[KnitKey],
) -> Result<std::collections::HashMap<KnitKey, Vec<u8>>, KnitError>
where
I: KnitIndex,
A: KnitAccess,
{
let details_map = index.get_build_details(keys)?;
let mut out = std::collections::HashMap::new();
for key in keys {
let Some(details) = details_map.get(key) else {
continue;
};
let raw = access.get_raw_record(&details.index_memo)?;
let header = parse_record_header_only(&raw)?;
out.insert(key.clone(), header.digest);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn refs<'a>(v: &'a [Vec<u8>]) -> Vec<&'a [u8]> {
v.iter().map(|l| l.as_slice()).collect()
}
#[test]
fn pure_rust_full_record_read_pipeline() {
let pairs = vec![
(b"r1".to_vec(), b"hello\n".to_vec()),
(b"r1".to_vec(), b"world\n".to_vec()),
];
let body = lower_fulltext(&pairs);
let (_, chunks) = record_to_data(b"v", b"DD", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let decompressed = decode_record_gz(&raw).unwrap();
let (header, body_lines) = parse_record_body_unchecked(&decompressed).unwrap();
let factory = KnitAnnotateFactory;
let content = factory
.parse_record(
header.version_id,
&body_lines,
KnitMethod::Fulltext,
false,
None,
)
.unwrap();
assert_eq!(content.lines, pairs);
assert_eq!(
content.text(),
vec![b"hello\n".to_vec(), b"world\n".to_vec()]
);
}
#[test]
fn pure_rust_delta_chain_apply_pipeline() {
let parent_pairs = vec![
(b"r1".to_vec(), b"a\n".to_vec()),
(b"r1".to_vec(), b"b\n".to_vec()),
];
let parent_body = lower_fulltext(&parent_pairs);
let (_, p_chunks) =
record_to_data(b"r1", b"D1", parent_body.len(), &parent_body, true).unwrap();
let parent_raw: Vec<u8> = p_chunks.into_iter().flatten().collect();
let delta = vec![DeltaHunk {
start: 1,
end: 2,
count: 1,
lines: vec![(b"r2".to_vec(), b"B\n".to_vec())],
}];
let delta_body = lower_line_delta_annotated(&delta);
let (_, d_chunks) =
record_to_data(b"r2", b"D2", delta_body.len(), &delta_body, true).unwrap();
let delta_raw: Vec<u8> = d_chunks.into_iter().flatten().collect();
let factory = KnitAnnotateFactory;
let parent_decomp = decode_record_gz(&parent_raw).unwrap();
let (parent_header, parent_lines) = parse_record_body_unchecked(&parent_decomp).unwrap();
let parent_content = factory
.parse_record(
parent_header.version_id,
&parent_lines,
KnitMethod::Fulltext,
false,
None,
)
.unwrap();
let delta_decomp = decode_record_gz(&delta_raw).unwrap();
let (delta_header, delta_lines) = parse_record_body_unchecked(&delta_decomp).unwrap();
let target_content = factory
.parse_record(
delta_header.version_id,
&delta_lines,
KnitMethod::LineDelta,
false,
Some(&parent_content),
)
.unwrap();
assert_eq!(
target_content.text(),
vec![b"a\n".to_vec(), b"B\n".to_vec()]
);
}
#[derive(Default)]
struct MockKnit {
records: std::collections::HashMap<KnitKey, KnitRecordDetails>,
bytes: std::collections::HashMap<KnitIndexMemo, Vec<u8>>,
}
impl MockKnit {
fn add_record(&mut self, key: KnitKey, details: KnitRecordDetails, raw: Vec<u8>) {
self.bytes.insert(details.index_memo.clone(), raw);
self.records.insert(key, details);
}
}
impl KnitIndex for MockKnit {
fn get_build_details(
&self,
keys: &[KnitKey],
) -> Result<std::collections::HashMap<KnitKey, KnitRecordDetails>, KnitError> {
let mut out = std::collections::HashMap::new();
for k in keys {
if let Some(d) = self.records.get(k) {
out.insert(k.clone(), d.clone());
}
}
Ok(out)
}
}
impl KnitAccess for MockKnit {
fn get_raw_record(&self, memo: &KnitIndexMemo) -> Result<Vec<u8>, KnitError> {
self.bytes
.get(memo)
.cloned()
.ok_or_else(|| KnitError::BadIndexValue(memo.path.as_bytes().to_vec()))
}
}
fn build_fulltext_record(
version_id: &[u8],
annotated: &[AnnotatedLine],
) -> (Vec<u8>, KnitIndexMemo) {
let body = lower_fulltext(annotated);
let (_, chunks) = record_to_data(version_id, b"DIGEST", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let memo = KnitIndexMemo {
path: format!("rec/{}", String::from_utf8_lossy(version_id)),
offset: 0,
length: raw.len(),
};
(raw, memo)
}
fn build_delta_record(
version_id: &[u8],
delta: &[DeltaHunk<AnnotatedLine>],
) -> (Vec<u8>, KnitIndexMemo) {
let body = lower_line_delta_annotated(delta);
let (_, chunks) = record_to_data(version_id, b"DIGEST", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let memo = KnitIndexMemo {
path: format!("rec/{}", String::from_utf8_lossy(version_id)),
offset: 0,
length: raw.len(),
};
(raw, memo)
}
#[test]
fn get_text_returns_fulltext_record_via_traits() {
let mut knit = MockKnit::default();
let key: KnitKey = vec![b"file".to_vec(), b"v1".to_vec()];
let pairs = vec![
(b"v1".to_vec(), b"alpha\n".to_vec()),
(b"v1".to_vec(), b"beta\n".to_vec()),
];
let (raw, memo) = build_fulltext_record(b"v1", &pairs);
knit.add_record(
key.clone(),
KnitRecordDetails {
method: KnitMethod::Fulltext,
noeol: false,
index_memo: memo,
compression_parent: None,
parents: vec![],
},
raw,
);
let factory = KnitAnnotateFactory;
let text = get_text(&knit, &knit, &factory, &key).unwrap();
assert_eq!(text, b"alpha\nbeta\n".to_vec());
}
#[test]
fn get_text_walks_two_step_delta_chain_via_traits() {
let mut knit = MockKnit::default();
let parent_key: KnitKey = vec![b"file".to_vec(), b"v1".to_vec()];
let child_key: KnitKey = vec![b"file".to_vec(), b"v2".to_vec()];
let parent_pairs = vec![
(b"v1".to_vec(), b"a\n".to_vec()),
(b"v1".to_vec(), b"b\n".to_vec()),
];
let (parent_raw, parent_memo) = build_fulltext_record(b"v1", &parent_pairs);
knit.add_record(
parent_key.clone(),
KnitRecordDetails {
method: KnitMethod::Fulltext,
noeol: false,
index_memo: parent_memo,
compression_parent: None,
parents: vec![],
},
parent_raw,
);
let delta = vec![DeltaHunk {
start: 1,
end: 2,
count: 1,
lines: vec![(b"v2".to_vec(), b"B\n".to_vec())],
}];
let (delta_raw, delta_memo) = build_delta_record(b"v2", &delta);
knit.add_record(
child_key.clone(),
KnitRecordDetails {
method: KnitMethod::LineDelta,
noeol: false,
index_memo: delta_memo,
compression_parent: Some(parent_key.clone()),
parents: vec![parent_key.clone()],
},
delta_raw,
);
let factory = KnitAnnotateFactory;
let text = get_text(&knit, &knit, &factory, &child_key).unwrap();
assert_eq!(text, b"a\nB\n".to_vec());
}
#[test]
fn get_sha1s_returns_digests_without_parsing_bodies() {
let mut knit = MockKnit::default();
let key_a: KnitKey = vec![b"file".to_vec(), b"a".to_vec()];
let key_b: KnitKey = vec![b"file".to_vec(), b"b".to_vec()];
let pairs_a = vec![(b"a".to_vec(), b"hello\n".to_vec())];
let pairs_b = vec![(b"b".to_vec(), b"world\n".to_vec())];
let (raw_a, memo_a) = build_fulltext_record(b"a", &pairs_a);
let (raw_b, memo_b) = build_fulltext_record(b"b", &pairs_b);
knit.add_record(
key_a.clone(),
KnitRecordDetails {
method: KnitMethod::Fulltext,
noeol: false,
index_memo: memo_a,
compression_parent: None,
parents: vec![],
},
raw_a,
);
knit.add_record(
key_b.clone(),
KnitRecordDetails {
method: KnitMethod::Fulltext,
noeol: false,
index_memo: memo_b,
compression_parent: None,
parents: vec![],
},
raw_b,
);
let result = get_sha1s(&knit, &knit, &[key_a.clone(), key_b.clone()]).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[&key_a], b"DIGEST");
assert_eq!(result[&key_b], b"DIGEST");
}
#[test]
fn get_sha1s_skips_missing_keys() {
let knit = MockKnit::default();
let key: KnitKey = vec![b"missing".to_vec()];
let result = get_sha1s(&knit, &knit, &[key]).unwrap();
assert!(result.is_empty());
}
#[test]
fn get_text_propagates_missing_key() {
let knit = MockKnit::default();
let key: KnitKey = vec![b"missing".to_vec()];
let factory = KnitAnnotateFactory;
assert!(get_text(&knit, &knit, &factory, &key).is_err());
}
#[test]
fn annotated_content_text_strips_origins() {
let content = AnnotatedKnitContent::new(vec![
(b"r1".to_vec(), b"first\n".to_vec()),
(b"r2".to_vec(), b"second\n".to_vec()),
]);
assert_eq!(
content.text(),
vec![b"first\n".to_vec(), b"second\n".to_vec()]
);
}
#[test]
fn annotated_content_text_honors_strip_eol() {
let mut content = AnnotatedKnitContent::new(vec![
(b"r1".to_vec(), b"first\n".to_vec()),
(b"r2".to_vec(), b"second\n".to_vec()),
]);
content.set_should_strip_eol(true);
assert_eq!(
content.text(),
vec![b"first\n".to_vec(), b"second".to_vec()]
);
let annotated = content.annotate();
assert_eq!(annotated.last().unwrap().1, b"second");
}
#[test]
fn annotated_content_apply_delta_splices_lines() {
let mut content = AnnotatedKnitContent::new(vec![
(b"r1".to_vec(), b"a\n".to_vec()),
(b"r1".to_vec(), b"b\n".to_vec()),
(b"r1".to_vec(), b"c\n".to_vec()),
(b"r1".to_vec(), b"d\n".to_vec()),
]);
let delta = vec![DeltaHunk {
start: 1,
end: 3,
count: 2,
lines: vec![
(b"r2".to_vec(), b"B\n".to_vec()),
(b"r2".to_vec(), b"C\n".to_vec()),
],
}];
content.apply_delta(&delta, b"r2");
let texts = content.text();
assert_eq!(
texts,
vec![
b"a\n".to_vec(),
b"B\n".to_vec(),
b"C\n".to_vec(),
b"d\n".to_vec(),
]
);
}
#[test]
fn plain_content_apply_delta_updates_version_id() {
let mut content =
PlainKnitContent::new(vec![b"a\n".to_vec(), b"b\n".to_vec()], b"r1".to_vec());
let delta = vec![DeltaHunk {
start: 0,
end: 0,
count: 1,
lines: vec![b"first\n".to_vec()],
}];
content.apply_delta(&delta, b"r2");
assert_eq!(content.version_id, b"r2");
assert_eq!(
content.text(),
vec![b"first\n".to_vec(), b"a\n".to_vec(), b"b\n".to_vec()]
);
}
#[test]
fn plain_content_annotate_uses_version_id() {
let content =
PlainKnitContent::new(vec![b"a\n".to_vec(), b"b\n".to_vec()], b"rev".to_vec());
let annotated = content.annotate();
assert_eq!(annotated.len(), 2);
assert_eq!(annotated[0].0, b"rev");
assert_eq!(annotated[0].1, b"a\n");
assert_eq!(annotated[1].0, b"rev");
}
#[test]
fn factory_parse_fulltext_round_trips_via_annotated_content() {
let pairs = vec![
(b"r1".to_vec(), b"alpha\n".to_vec()),
(b"r2".to_vec(), b"beta\n".to_vec()),
];
let body = lower_fulltext(&pairs);
let body_refs: Vec<&[u8]> = body.iter().map(|l| l.as_slice()).collect();
let factory = KnitAnnotateFactory;
let content = factory
.parse_record(b"v", &body_refs, KnitMethod::Fulltext, false, None)
.unwrap();
assert_eq!(content.lines, pairs);
assert!(!content.should_strip_eol());
}
#[test]
fn factory_parse_record_applies_delta_to_base() {
let base = AnnotatedKnitContent::new(vec![
(b"r1".to_vec(), b"a\n".to_vec()),
(b"r1".to_vec(), b"b\n".to_vec()),
]);
let body = vec![b"1,2,1\n".to_vec(), b"r2 B\n".to_vec()];
let body_refs: Vec<&[u8]> = body.iter().map(|l| l.as_slice()).collect();
let factory = KnitAnnotateFactory;
let content = factory
.parse_record(b"r2", &body_refs, KnitMethod::LineDelta, false, Some(&base))
.unwrap();
assert_eq!(content.text(), vec![b"a\n".to_vec(), b"B\n".to_vec()]);
}
#[test]
fn plain_factory_parses_line_delta_record() {
let base = PlainKnitContent::new(vec![b"a\n".to_vec(), b"b\n".to_vec()], b"r1".to_vec());
let body = vec![b"1,2,1\n".to_vec(), b"B\n".to_vec()];
let body_refs: Vec<&[u8]> = body.iter().map(|l| l.as_slice()).collect();
let factory = KnitPlainFactory;
let content = factory
.parse_record(b"r2", &body_refs, KnitMethod::LineDelta, false, Some(&base))
.unwrap();
assert_eq!(content.version_id, b"r2");
assert_eq!(content.text(), vec![b"a\n".to_vec(), b"B\n".to_vec()]);
}
#[test]
fn factory_line_delta_without_base_is_an_error() {
let factory = KnitAnnotateFactory;
let err = factory
.parse_record(b"v", &[], KnitMethod::LineDelta, false, None)
.unwrap_err();
assert!(matches!(err, KnitError::BadIndexValue(_)));
}
#[test]
fn plain_factory_parses_fulltext_into_plain_content() {
let factory = KnitPlainFactory;
let body = vec![b"alpha\n".to_vec(), b"beta\n".to_vec()];
let body_refs: Vec<&[u8]> = body.iter().map(|l| l.as_slice()).collect();
let content = factory
.parse_record(b"v", &body_refs, KnitMethod::Fulltext, true, None)
.unwrap();
assert_eq!(content.version_id, b"v");
assert!(content.should_strip_eol());
assert_eq!(content.text(), vec![b"alpha\n".to_vec(), b"beta".to_vec()]);
}
#[test]
fn fulltext_round_trip() {
let content: Vec<AnnotatedLine> = vec![
(b"rev1".to_vec(), b"first line\n".to_vec()),
(b"rev2".to_vec(), b"second line\n".to_vec()),
];
let bytes = lower_fulltext(&content);
assert_eq!(
bytes,
vec![
b"rev1 first line\n".to_vec(),
b"rev2 second line\n".to_vec(),
]
);
let parsed = parse_fulltext(&refs(&bytes)).unwrap();
assert_eq!(parsed, content);
}
#[test]
fn fulltext_rejects_missing_origin() {
let lines = vec![b"no-space-here".as_slice()];
assert!(matches!(
parse_fulltext(&lines),
Err(KnitError::MissingOrigin(_))
));
}
#[test]
fn delta_annotated_round_trip() {
let delta = vec![
DeltaHunk {
start: 0,
end: 1,
count: 2,
lines: vec![
(b"r1".to_vec(), b"alpha\n".to_vec()),
(b"r1".to_vec(), b"beta\n".to_vec()),
],
},
DeltaHunk {
start: 5,
end: 5,
count: 1,
lines: vec![(b"r2".to_vec(), b"gamma\n".to_vec())],
},
];
let bytes = lower_line_delta_annotated(&delta);
assert_eq!(
bytes,
vec![
b"0,1,2\n".to_vec(),
b"r1 alpha\n".to_vec(),
b"r1 beta\n".to_vec(),
b"5,5,1\n".to_vec(),
b"r2 gamma\n".to_vec(),
]
);
let parsed = parse_line_delta_annotated(&refs(&bytes)).unwrap();
assert_eq!(parsed, delta);
}
#[test]
fn delta_raw_round_trip() {
let delta = vec![
DeltaHunk {
start: 0,
end: 0,
count: 2,
lines: vec![b"one\n".to_vec(), b"two\n".to_vec()],
},
DeltaHunk {
start: 4,
end: 5,
count: 1,
lines: vec![b"three\n".to_vec()],
},
];
let bytes = lower_line_delta_raw(&delta);
assert_eq!(
bytes,
vec![
b"0,0,2\n".to_vec(),
b"one\n".to_vec(),
b"two\n".to_vec(),
b"4,5,1\n".to_vec(),
b"three\n".to_vec(),
]
);
let parsed = parse_line_delta_raw(&refs(&bytes)).unwrap();
assert_eq!(parsed, delta);
}
#[test]
fn delta_plain_strips_origin() {
let bytes: Vec<Vec<u8>> = vec![
b"0,1,2\n".to_vec(),
b"r1 alpha\n".to_vec(),
b"r1 beta\n".to_vec(),
];
let parsed = parse_line_delta_plain(&refs(&bytes)).unwrap();
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].start, 0);
assert_eq!(parsed[0].end, 1);
assert_eq!(parsed[0].count, 2);
assert_eq!(
parsed[0].lines,
vec![b"alpha\n".to_vec(), b"beta\n".to_vec()]
);
}
#[test]
fn delta_rejects_bad_header() {
let bytes = vec![b"not,a,number\n".as_slice()];
assert!(matches!(
parse_line_delta_annotated(&bytes),
Err(KnitError::BadDeltaHeader(_))
));
}
#[test]
fn delta_rejects_truncated() {
let bytes = vec![b"0,0,3\n".as_slice(), b"r1 one\n".as_slice()];
assert_eq!(
parse_line_delta_annotated(&bytes),
Err(KnitError::TruncatedDelta)
);
}
fn lines_with_nl(text: &[u8]) -> Vec<Vec<u8>> {
text.split(|&b| b == b'\n')
.filter(|l| !l.is_empty())
.map(|l| {
let mut v = l.to_vec();
v.push(b'\n');
v
})
.collect()
}
#[test]
fn line_delta_blocks_equal_inputs() {
let source = lines_with_nl(b"a\nb\nc\n");
let target = source.clone();
let delta: Vec<(usize, usize, usize)> = vec![];
let blocks = get_line_delta_blocks(&delta, &refs(&source), &refs(&target));
assert_eq!(blocks, vec![(0, 0, 3), (3, 3, 0)]);
}
#[test]
fn line_delta_blocks_noeol_shrinks_trailing_run() {
let source: Vec<Vec<u8>> = vec![b"a\n".to_vec(), b"b\n".to_vec(), b"c".to_vec()];
let target: Vec<Vec<u8>> = vec![
b"a\n".to_vec(),
b"b\n".to_vec(),
b"c\n".to_vec(),
b"d\n".to_vec(),
];
let delta = vec![(2usize, 3usize, 2usize)];
let blocks = get_line_delta_blocks(&delta, &refs(&source), &refs(&target));
assert_eq!(blocks, vec![(0, 0, 2), (3, 4, 0)]);
}
#[test]
fn line_delta_blocks_replace_middle_line() {
let source = lines_with_nl(b"a\nb\nc\n");
let target = lines_with_nl(b"a\nX\nc\n");
let delta = vec![(1usize, 2usize, 1usize)];
let blocks = get_line_delta_blocks(&delta, &refs(&source), &refs(&target));
assert_eq!(blocks, vec![(0, 0, 1), (2, 2, 1), (3, 3, 0)]);
}
#[test]
fn network_header_no_parents_no_eol() {
let bytes = b"knit-ft-gz\nfile-id\x00rev\nNone:\nNDATA";
let header = parse_network_record_header(bytes, 11).unwrap();
assert_eq!(header.key, vec![b"file-id".as_slice(), b"rev".as_slice()]);
assert!(header.parents.is_none());
assert!(header.noeol);
assert_eq!(header.raw_record, b"DATA");
}
#[test]
fn network_header_with_parents_and_eol() {
let bytes = b"knit-delta-gz\nf\x00r\nf\x00p1\tf\x00p2\nYBODY";
let header = parse_network_record_header(bytes, 14).unwrap();
let parents = header.parents.unwrap();
assert_eq!(
parents,
vec![
vec![b"f".as_slice(), b"p1".as_slice()],
vec![b"f".as_slice(), b"p2".as_slice()],
]
);
assert!(!header.noeol);
assert_eq!(header.raw_record, b"BODY");
}
#[test]
fn network_header_empty_parents_list_is_some_empty() {
let bytes = b"knit-ft-gz\nk\n\nNX";
let header = parse_network_record_header(bytes, 11).unwrap();
assert_eq!(header.parents.unwrap().len(), 0);
assert_eq!(header.raw_record, b"X");
}
#[test]
fn split_keys_by_prefix_preserves_first_seen_order() {
let keys: Vec<Vec<Vec<u8>>> = vec![
vec![b"file-a".to_vec(), b"rev-1".to_vec()],
vec![b"file-b".to_vec(), b"rev-1".to_vec()],
vec![b"file-a".to_vec(), b"rev-2".to_vec()],
vec![b"lone-rev".to_vec()], vec![b"file-b".to_vec(), b"rev-2".to_vec()],
];
let (buckets, order) = split_keys_by_prefix(&keys);
assert_eq!(
order,
vec![b"file-a".to_vec(), b"file-b".to_vec(), Vec::<u8>::new()]
);
assert_eq!(buckets.len(), 3);
assert_eq!(buckets[0].0, b"file-a".to_vec());
assert_eq!(buckets[0].1.len(), 2);
assert_eq!(buckets[0].1[0], keys[0].as_slice());
assert_eq!(buckets[0].1[1], keys[2].as_slice());
assert_eq!(buckets[2].0, Vec::<u8>::new());
assert_eq!(buckets[2].1, vec![keys[3].as_slice()]);
}
#[test]
fn split_keys_by_prefix_empty_input() {
let keys: Vec<Vec<Vec<u8>>> = vec![];
let (buckets, order) = split_keys_by_prefix(&keys);
assert!(buckets.is_empty());
assert!(order.is_empty());
}
#[test]
fn knit_delta_closure_wire_matches_python_layout() {
let key1: &[&[u8]] = &[b"file", b"rev1"];
let key2: &[&[u8]] = &[b"rev2"];
let emit_keys: &[&[&[u8]]] = &[key1, key2];
let parent_a: &[&[u8]] = &[b"file", b"p0"];
let rec2_parents: &[&[&[u8]]] = &[parent_a];
let next2: &[&[u8]] = &[b"file", b"rev1"];
let records = [
KnitDeltaClosureRecord {
key: key1,
parents: None,
method: b"line-delta",
noeol: true,
next: None,
record_bytes: b"BODY-1",
},
KnitDeltaClosureRecord {
key: key2,
parents: Some(rec2_parents),
method: b"fulltext",
noeol: false,
next: Some(next2),
record_bytes: b"BODY-2",
},
];
let out = build_knit_delta_closure_wire(true, emit_keys, &records);
let mut expected: Vec<u8> = Vec::new();
expected.extend_from_slice(b"knit-delta-closure\n");
expected.extend_from_slice(b"annotated\n");
expected.extend_from_slice(b"file\x00rev1\trev2\n");
expected.extend_from_slice(b"file\x00rev1\n");
expected.extend_from_slice(b"None:\n");
expected.extend_from_slice(b"line-delta\n");
expected.extend_from_slice(b"T\n");
expected.extend_from_slice(b"\n"); expected.extend_from_slice(b"6\n"); expected.extend_from_slice(b"BODY-1");
expected.extend_from_slice(b"rev2\n");
expected.extend_from_slice(b"file\x00p0\n");
expected.extend_from_slice(b"fulltext\n");
expected.extend_from_slice(b"F\n");
expected.extend_from_slice(b"file\x00rev1\n");
expected.extend_from_slice(b"6\n");
expected.extend_from_slice(b"BODY-2");
assert_eq!(out, expected);
}
#[test]
fn knit_delta_closure_wire_unannotated_has_blank_flag_line() {
let emit_keys: &[&[&[u8]]] = &[];
let out = build_knit_delta_closure_wire(false, emit_keys, &[]);
assert_eq!(out, b"knit-delta-closure\n\n\n".to_vec());
}
#[test]
fn build_network_record_round_trips_none_parents() {
let key: &[&[u8]] = &[b"file-id", b"rev"];
let raw = build_network_record(b"knit-ft-gz", key, NO_PARENTS, true, b"DATA");
let line_end = b"knit-ft-gz\n".len();
let parsed = parse_network_record_header(&raw, line_end).unwrap();
assert_eq!(parsed.key, vec![&b"file-id"[..], &b"rev"[..]]);
assert!(parsed.parents.is_none());
assert!(parsed.noeol);
assert_eq!(parsed.raw_record, b"DATA");
}
#[test]
fn build_network_record_round_trips_with_parents_and_eol() {
let key: &[&[u8]] = &[b"f", b"r"];
let p1: &[&[u8]] = &[b"f", b"p1"];
let p2: &[&[u8]] = &[b"f", b"p2"];
let parents: &[&[&[u8]]] = &[p1, p2];
let raw = build_network_record(b"knit-delta-gz", key, Some(parents), false, b"BODY");
let line_end = b"knit-delta-gz\n".len();
let parsed = parse_network_record_header(&raw, line_end).unwrap();
assert_eq!(parsed.parents.unwrap().len(), 2);
assert!(!parsed.noeol);
assert_eq!(parsed.raw_record, b"BODY");
}
#[test]
fn build_network_record_single_key_segment() {
let key: &[&[u8]] = &[b"only"];
let raw = build_network_record(b"knit-ft-gz", key, NO_PARENTS, true, b"X");
assert_eq!(raw, b"knit-ft-gz\nonly\nNone:\nNX".to_vec());
}
#[test]
fn network_header_rejects_missing_noeol_byte() {
let bytes = b"knit-ft-gz\nk\nNone:\n";
let err = parse_network_record_header(bytes, 11).unwrap_err();
assert_eq!(err, KnitError::NetworkMissingNoEolByte);
}
fn build_record(version_id: &[u8], digest: &[u8], body: &[&[u8]]) -> Vec<u8> {
let mut header = Vec::new();
header.extend_from_slice(b"version ");
header.extend_from_slice(version_id);
header.extend_from_slice(format!(" {} ", body.len()).as_bytes());
header.extend_from_slice(digest);
header.push(b'\n');
let mut end = Vec::new();
end.extend_from_slice(b"end ");
end.extend_from_slice(version_id);
end.push(b'\n');
let mut chunks: Vec<&[u8]> = vec![&header];
chunks.extend_from_slice(body);
chunks.push(&end);
let gz = crate::tuned_gzip::chunks_to_gzip(chunks.iter().copied());
gz.into_iter().flatten().collect()
}
#[test]
fn parse_record_unchecked_round_trip() {
let body: &[&[u8]] = &[b"first line\n", b"second line\n"];
let raw = build_record(b"rev-1", b"DIGEST", body);
let (rec, contents) = parse_record_unchecked(&raw).unwrap();
assert_eq!(rec.method, b"version");
assert_eq!(rec.version_id, b"rev-1");
assert_eq!(rec.count, 2);
assert_eq!(rec.digest, b"DIGEST");
assert_eq!(
contents,
vec![b"first line\n".to_vec(), b"second line\n".to_vec()]
);
}
#[test]
fn parse_record_unchecked_zero_body() {
let raw = build_record(b"rev-0", b"DD", &[]);
let (rec, contents) = parse_record_unchecked(&raw).unwrap();
assert_eq!(rec.count, 0);
assert!(contents.is_empty());
}
#[test]
fn parse_record_unchecked_wrong_line_count() {
let mut header = b"version rev-x 5 DD\n".to_vec();
let body = b"only one\n".to_vec();
let end = b"end rev-x\n".to_vec();
let chunks: Vec<&[u8]> = vec![&header[..], &body[..], &end[..]];
let gz = crate::tuned_gzip::chunks_to_gzip(chunks.iter().copied());
let raw: Vec<u8> = gz.into_iter().flatten().collect();
let _ = &mut header;
let err = parse_record_unchecked(&raw).unwrap_err();
assert_eq!(
err,
KnitError::LineCount {
declared: 5,
actual: 1,
}
);
}
#[test]
fn parse_record_checks_version_match() {
let body: &[&[u8]] = &[b"a\n", b"b\n"];
let raw = build_record(b"rev-9", b"DIGEST", body);
let (lines, digest) = parse_record(b"rev-9", &raw).unwrap();
assert_eq!(lines, vec![b"a\n".to_vec(), b"b\n".to_vec()]);
assert_eq!(digest, b"DIGEST");
}
#[test]
fn parse_record_rejects_version_mismatch() {
let raw = build_record(b"got-this", b"DD", &[b"x\n"]);
let err = parse_record(b"wanted-that", &raw).unwrap_err();
assert_eq!(
err,
KnitError::UnexpectedVersion {
wanted: b"wanted-that".to_vec(),
got: b"got-this".to_vec(),
}
);
}
#[test]
fn parse_record_header_only_ignores_line_count_mismatch() {
let header = b"version rev-id-1 2 DIGEST\n".to_vec();
let body = b"foo\n".to_vec();
let end = b"end rev-id-1\n".to_vec();
let chunks: Vec<&[u8]> = vec![&header, &body, &end];
let gz = crate::tuned_gzip::chunks_to_gzip(chunks.into_iter());
let raw: Vec<u8> = gz.into_iter().flatten().collect();
assert!(parse_record_unchecked(&raw).is_err());
let rec = parse_record_header_only(&raw).unwrap();
assert_eq!(rec.version_id, b"rev-id-1");
assert_eq!(rec.count, 2);
assert_eq!(rec.digest, b"DIGEST");
}
#[test]
fn parse_record_unchecked_reports_gzip_errors_as_knit_error() {
let err = parse_record_unchecked(b"definitely not gzip").unwrap_err();
assert!(matches!(err, KnitError::Gzip(_)));
assert!(err.to_string().contains("corrupt compressed record"));
}
#[test]
fn readlines_iter_matches_collected_and_handles_unterminated_tail() {
let data = b"alpha\nbeta\ngamma";
let streamed: Vec<&[u8]> = ReadLines::new(data).collect();
assert_eq!(
streamed,
vec![&b"alpha\n"[..], &b"beta\n"[..], &b"gamma"[..]]
);
assert_eq!(streamed, readlines(data));
assert!(ReadLines::new(b"").next().is_none());
assert_eq!(readlines(b"just-one"), vec![&b"just-one"[..]]);
assert_eq!(readlines(b"\n"), vec![&b"\n"[..]]);
}
#[test]
fn parse_knit_index_value_handles_noeol_flag() {
let v = parse_knit_index_value(b"N123 4567").unwrap();
assert!(v.noeol);
assert_eq!(v.pos, 123);
assert_eq!(v.size, 4567);
let v = parse_knit_index_value(b" 5 10").unwrap();
assert!(!v.noeol);
assert_eq!(v.pos, 5);
assert_eq!(v.size, 10);
}
#[test]
fn parse_knit_index_value_rejects_garbage() {
assert_eq!(
parse_knit_index_value(b"").unwrap_err(),
KnitError::BadIndexValue(b"".to_vec())
);
assert_eq!(
parse_knit_index_value(b"Nfoo bar").unwrap_err(),
KnitError::BadIndexValue(b"Nfoo bar".to_vec())
);
assert_eq!(
parse_knit_index_value(b"N5").unwrap_err(),
KnitError::BadIndexValue(b"N5".to_vec())
);
}
fn batch_from_chain<'a>(
chain: &'a std::collections::HashMap<&'static str, Option<&'static str>>,
keys: &[&'static str],
) -> ClosureBatch<&'static str, &'static str> {
ClosureBatch {
present: keys
.iter()
.filter_map(|k| chain.get(k).map(|p| (*k, (*p, *k))))
.collect(),
missing: keys
.iter()
.filter(|k| !chain.contains_key(*k))
.copied()
.collect(),
}
}
#[test]
fn walk_compression_closure_follows_chain_until_fulltext() {
let chain: std::collections::HashMap<&'static str, Option<&'static str>> =
vec![("a", Some("b")), ("b", Some("c")), ("c", None)]
.into_iter()
.collect();
let result =
walk_compression_closure(vec!["a"], false, |batch| batch_from_chain(&chain, batch))
.unwrap();
let learned: std::collections::HashSet<&'static str> = result.keys().copied().collect();
let expected: std::collections::HashSet<&'static str> =
vec!["a", "b", "c"].into_iter().collect();
assert_eq!(learned, expected);
assert_eq!(result[&"a"], "a");
assert_eq!(result[&"c"], "c");
}
#[test]
fn walk_compression_closure_dedups_shared_parents() {
let chain: std::collections::HashMap<&'static str, Option<&'static str>> =
vec![("c1", Some("p")), ("c2", Some("p")), ("p", None)]
.into_iter()
.collect();
let mut batches: usize = 0;
let result = walk_compression_closure(vec!["c1", "c2"], false, |batch| {
batches += 1;
batch_from_chain(&chain, batch)
})
.unwrap();
assert_eq!(batches, 2);
let learned: std::collections::HashSet<&'static str> = result.keys().copied().collect();
let expected: std::collections::HashSet<&'static str> =
vec!["c1", "c2", "p"].into_iter().collect();
assert_eq!(learned, expected);
}
#[test]
fn walk_compression_closure_reports_missing_when_not_allowed() {
let err =
walk_compression_closure::<&'static str, &'static str, _>(vec!["x"], false, |_batch| {
ClosureBatch {
present: Default::default(),
missing: vec!["x"].into_iter().collect(),
}
})
.unwrap_err();
let expected: std::collections::HashSet<&'static str> = vec!["x"].into_iter().collect();
assert_eq!(err, expected);
}
#[test]
fn walk_compression_closure_skips_missing_when_allowed() {
let result = walk_compression_closure::<&'static str, &'static str, _>(
vec!["x", "y"],
true,
|batch| {
let mut present = std::collections::HashMap::new();
let mut missing = std::collections::HashSet::new();
for k in batch {
if *k == "y" {
present.insert(*k, (None, *k));
} else {
missing.insert(*k);
}
}
ClosureBatch { present, missing }
},
)
.unwrap();
let learned: std::collections::HashSet<&'static str> = result.keys().copied().collect();
let expected: std::collections::HashSet<&'static str> = vec!["y"].into_iter().collect();
assert_eq!(learned, expected);
}
#[test]
fn should_use_delta_finds_fulltext_and_picks_delta() {
let chain: std::collections::HashMap<&str, ChainStep<&'static str>> = vec![
(
"a",
ChainStep {
size: 10,
compression_parent: Some("b"),
},
),
(
"b",
ChainStep {
size: 10,
compression_parent: Some("c"),
},
),
(
"c",
ChainStep {
size: 100,
compression_parent: None,
},
),
]
.into_iter()
.collect();
let decision = should_use_delta("a", 5, |k| chain.get(k).cloned());
assert_eq!(decision, DeltaDecision::UseDelta);
assert!(decision.should_use_delta());
}
#[test]
fn should_use_delta_picks_fulltext_when_delta_chain_is_bigger() {
let chain: std::collections::HashMap<&str, ChainStep<&'static str>> = vec![
(
"a",
ChainStep {
size: 100,
compression_parent: Some("b"),
},
),
(
"b",
ChainStep {
size: 100,
compression_parent: Some("c"),
},
),
(
"c",
ChainStep {
size: 50,
compression_parent: None,
},
),
]
.into_iter()
.collect();
assert_eq!(
should_use_delta("a", 5, |k| chain.get(k).cloned()),
DeltaDecision::FulltextSmaller
);
}
#[test]
fn should_use_delta_chain_too_long() {
let decision = should_use_delta("a", 3, |_| {
Some(ChainStep {
size: 5,
compression_parent: Some("a"),
})
});
assert_eq!(decision, DeltaDecision::ChainTooLong);
}
#[test]
fn should_use_delta_missing_parent_falls_back_to_fulltext() {
let decision = should_use_delta("a", 5, |_| None);
assert_eq!(decision, DeltaDecision::MissingParent);
assert!(!decision.should_use_delta());
}
#[test]
fn decode_kndx_options_picks_method_and_noeol() {
let opts: &[&[u8]] = &[b"fulltext"];
assert_eq!(
decode_kndx_options(opts).unwrap(),
(KnitMethod::Fulltext, false)
);
let opts: &[&[u8]] = &[b"line-delta", b"no-eol"];
assert_eq!(
decode_kndx_options(opts).unwrap(),
(KnitMethod::LineDelta, true)
);
let opts: &[&[u8]] = &[b"no-eol", b"some-future-flag", b"fulltext"];
assert_eq!(
decode_kndx_options(opts).unwrap(),
(KnitMethod::Fulltext, true)
);
}
#[test]
fn decode_kndx_options_rejects_missing_method() {
let opts: &[&[u8]] = &[b"no-eol"];
assert!(matches!(
decode_kndx_options(opts).unwrap_err(),
KnitError::BadIndexValue(_)
));
}
#[test]
fn decode_knit_build_details_picks_method_from_parent_count() {
let d = decode_knit_build_details(b" 0 10", false, 5).unwrap();
assert_eq!(d.method, KnitMethod::Fulltext);
assert_eq!(d.compression_parent, None);
let d = decode_knit_build_details(b" 0 10", true, 0).unwrap();
assert_eq!(d.method, KnitMethod::Fulltext);
assert_eq!(d.compression_parent, None);
let d = decode_knit_build_details(b"N0 10", true, 1).unwrap();
assert_eq!(d.method, KnitMethod::LineDelta);
assert!(d.noeol);
assert_eq!(d.compression_parent, Some(0));
assert_eq!(
decode_knit_build_details(b" 0 10", true, 2).unwrap_err(),
KnitError::TooManyCompressionParents(2)
);
}
#[test]
fn extract_annotated_fulltext_strips_origins_and_honors_noeol() {
let annotated: Vec<AnnotatedLine> = vec![
(b"r1".to_vec(), b"hello\n".to_vec()),
(b"r2".to_vec(), b"world\n".to_vec()),
];
let body = lower_fulltext(&annotated);
let (_, chunks) = record_to_data(b"v", b"DD", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let with_eol = extract_annotated_fulltext_to_plain_lines(&raw, false).unwrap();
assert_eq!(with_eol, vec![b"hello\n".to_vec(), b"world\n".to_vec()]);
let no_eol = extract_annotated_fulltext_to_plain_lines(&raw, true).unwrap();
assert_eq!(no_eol, vec![b"hello\n".to_vec(), b"world".to_vec()]);
}
#[test]
fn extract_plain_fulltext_lines_passes_through_with_noeol_strip() {
let body = vec![b"alpha\n".to_vec(), b"beta\n".to_vec()];
let (_, chunks) = record_to_data(b"v", b"DD", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let plain = extract_plain_fulltext_lines(&raw, false).unwrap();
assert_eq!(plain, vec![b"alpha\n".to_vec(), b"beta\n".to_vec()]);
let stripped = extract_plain_fulltext_lines(&raw, true).unwrap();
assert_eq!(stripped, vec![b"alpha\n".to_vec(), b"beta".to_vec()]);
}
#[test]
fn recompress_annotated_to_unannotated_fulltext_strips_origins() {
let annotated: Vec<AnnotatedLine> = vec![
(b"rev1".to_vec(), b"alpha\n".to_vec()),
(b"rev2".to_vec(), b"beta\n".to_vec()),
];
let body = lower_fulltext(&annotated);
let (_, chunks) = record_to_data(b"rev-id", b"DIGEST", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let unannotated_raw = recompress_annotated_to_unannotated_fulltext(&raw).unwrap();
let (header, body_lines) = parse_record_unchecked(&unannotated_raw).unwrap();
assert_eq!(header.version_id, b"rev-id");
assert_eq!(header.digest, b"DIGEST");
assert_eq!(header.count, 2);
assert_eq!(body_lines, vec![b"alpha\n".to_vec(), b"beta\n".to_vec()]);
}
#[test]
fn recompress_annotated_to_unannotated_delta_strips_origins() {
let delta = vec![DeltaHunk {
start: 0,
end: 1,
count: 2,
lines: vec![
(b"r1".to_vec(), b"alpha\n".to_vec()),
(b"r2".to_vec(), b"beta\n".to_vec()),
],
}];
let body = lower_line_delta_annotated(&delta);
let (_, chunks) = record_to_data(b"rev-id", b"DD", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let unannotated_raw = recompress_annotated_to_unannotated_delta(&raw).unwrap();
let (header, body_lines) = parse_record_unchecked(&unannotated_raw).unwrap();
assert_eq!(header.version_id, b"rev-id");
assert_eq!(header.digest, b"DD");
assert_eq!(body_lines.len(), 3);
assert_eq!(body_lines[0], b"0,1,2\n".to_vec());
assert_eq!(body_lines[1], b"alpha\n".to_vec());
assert_eq!(body_lines[2], b"beta\n".to_vec());
}
#[test]
fn parse_record_body_unchecked_borrows_from_buffer() {
let mut body = Vec::new();
body.extend_from_slice(b"version rev-x 2 DIG\n");
body.extend_from_slice(b"alpha\n");
body.extend_from_slice(b"beta\n");
body.extend_from_slice(b"end rev-x\n");
let (header, lines) = parse_record_body_unchecked(&body).unwrap();
assert_eq!(header.method, b"version");
assert_eq!(header.version_id, b"rev-x");
assert_eq!(header.count, 2);
assert_eq!(header.digest, b"DIG");
assert_eq!(lines, vec![&b"alpha\n"[..], &b"beta\n"[..]]);
let body_range = body.as_ptr_range();
for line in &lines {
let start = line.as_ptr();
assert!(start >= body_range.start && start < body_range.end);
}
}
#[test]
fn record_to_data_round_trip_via_parse() {
let body: Vec<Vec<u8>> = vec![b"alpha\n".to_vec(), b"beta\n".to_vec()];
let (len, chunks) = record_to_data(b"rev-7", b"DIGEST", body.len(), &body, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
assert_eq!(len, raw.len());
let (rec, contents) = parse_record_unchecked(&raw).unwrap();
assert_eq!(rec.version_id, b"rev-7");
assert_eq!(rec.count, 2);
assert_eq!(rec.digest, b"DIGEST");
assert_eq!(contents, body);
}
#[test]
fn record_to_data_rejects_missing_trailing_newline() {
let body: Vec<Vec<u8>> = vec![b"no-newline".to_vec()];
let err = record_to_data(b"rev", b"DD", 1, &body, false).unwrap_err();
assert_eq!(err, KnitError::MissingTrailingNewline);
}
#[test]
fn record_to_data_empty_body() {
let empty: Vec<Vec<u8>> = vec![];
let (_, chunks) = record_to_data(b"rev-0", b"DD", 0, &empty, true).unwrap();
let raw: Vec<u8> = chunks.into_iter().flatten().collect();
let (rec, contents) = parse_record_unchecked(&raw).unwrap();
assert_eq!(rec.count, 0);
assert!(contents.is_empty());
}
#[test]
fn parse_record_unchecked_bad_end_marker() {
let mut header = b"version rev-y 1 DD\n".to_vec();
let body = b"body\n".to_vec();
let end = b"end wrong-id\n".to_vec();
let chunks: Vec<&[u8]> = vec![&header[..], &body[..], &end[..]];
let gz = crate::tuned_gzip::chunks_to_gzip(chunks.iter().copied());
let raw: Vec<u8> = gz.into_iter().flatten().collect();
let _ = &mut header;
let err = parse_record_unchecked(&raw).unwrap_err();
assert_eq!(
err,
KnitError::BadEndMarker {
expected: b"end rev-y\n".to_vec(),
actual: b"end wrong-id\n".to_vec(),
}
);
}
#[test]
fn dictionary_compress_empty() {
let lookup = std::collections::HashMap::new();
let suffixes: Vec<&[u8]> = vec![];
assert_eq!(dictionary_compress_suffixes(&suffixes, &lookup), b"");
}
#[test]
fn dictionary_compress_all_cached() {
let mut lookup = std::collections::HashMap::new();
lookup.insert(&b"rev-a"[..], 0u64);
lookup.insert(&b"rev-b"[..], 3u64);
let suffixes: Vec<&[u8]> = vec![b"rev-a", b"rev-b"];
assert_eq!(dictionary_compress_suffixes(&suffixes, &lookup), b"0 3");
}
#[test]
fn dictionary_compress_mixed_and_fallback() {
let mut lookup = std::collections::HashMap::new();
lookup.insert(&b"rev-a"[..], 12u64);
let suffixes: Vec<&[u8]> = vec![b"rev-a", b"rev-ghost", b"rev-a"];
assert_eq!(
dictionary_compress_suffixes(&suffixes, &lookup),
b"12 .rev-ghost 12"
);
}
}