use std::{collections::VecDeque, sync::Arc};
#[cfg(not(feature = "diff_rabin"))]
use rayon::prelude::*;
use super::header::encode_one_object;
#[cfg(feature = "diff_rabin")]
use super::sort::multi_point_similar;
#[cfg(not(feature = "diff_rabin"))]
use super::sort::{calc_hash, cheap_similar};
use crate::{
delta,
errors::GitError,
internal::{
object::types::ObjectType,
pack::{entry::Entry, index_entry::IndexEntry},
},
zstdelta,
};
const MAX_CHAIN_LEN: usize = 50;
const MIN_DELTA_RATE: f64 = 0.5;
#[cfg(feature = "diff_rabin")]
pub(crate) struct DeltaWindowEntry {
pub(crate) entry: Entry,
pub(crate) offset: usize,
pub(crate) data_arc: Option<Arc<[u8]>>,
pub(crate) rabin_index: Option<delta::RabinDeltaIndex>,
}
#[cfg(not(feature = "diff_rabin"))]
pub(crate) struct DeltaWindowEntry {
pub(crate) entry: Entry,
pub(crate) offset: usize,
}
impl DeltaWindowEntry {
pub(crate) fn new(entry: Entry, offset: usize) -> Self {
Self {
entry,
offset,
#[cfg(feature = "diff_rabin")]
data_arc: None,
#[cfg(feature = "diff_rabin")]
rabin_index: None,
}
}
}
impl super::PackEncoder {
#[cfg_attr(not(feature = "diff_rabin"), allow(unused_variables))]
pub(super) fn try_as_offset_delta(
mut bucket: Vec<Entry>,
window_size: usize,
enable_zstdelta: bool,
enable_rabin: bool,
disable_prefilter: bool,
) -> Result<Vec<(Vec<u8>, IndexEntry)>, GitError> {
let mut current_offset = 0usize;
let mut window: VecDeque<DeltaWindowEntry> = VecDeque::with_capacity(window_size);
let mut res: Vec<(Vec<u8>, IndexEntry)> = Vec::with_capacity(bucket.len());
for entry in bucket.iter_mut() {
let mut best_base: Option<&DeltaWindowEntry> = None;
let mut best_rate: f64 = 0.0;
#[cfg(feature = "diff_rabin")]
{
let trg_size = entry.data.len();
let pre_filtered_idxs: Vec<usize> = window
.iter()
.enumerate()
.rev() .filter(|(_i, try_base)| {
let src_size = try_base.entry.data.len();
try_base.entry.obj_type == entry.obj_type
&& try_base.entry.chain_len < MAX_CHAIN_LEN
&& try_base.entry.hash != entry.hash
&& trg_size >= src_size / 32
&& src_size.saturating_sub(trg_size.min(src_size))
< trg_size / 2
&& (disable_prefilter
|| multi_point_similar(&try_base.entry.data, &entry.data))
})
.map(|(i, _)| i)
.collect();
if !pre_filtered_idxs.is_empty() {
let max_delta_size = trg_size.saturating_sub(trg_size / 2 + 20);
let mut best_delta_size = max_delta_size;
let mut best_idx: Option<usize> = None;
for &idx in &pre_filtered_idxs {
if window[idx].rabin_index.is_none() {
let data_arc = match window[idx].data_arc.take() {
Some(arc) => arc,
None => {
let arc: Arc<[u8]> = window[idx].entry.data.clone().into();
arc
}
};
match delta::create_delta_index_arc(Arc::clone(&data_arc)) {
Some(new_index) => {
window[idx].data_arc = Some(data_arc);
window[idx].rabin_index = Some(new_index);
}
None => {
window[idx].data_arc = Some(data_arc);
continue;
}
}
}
let delta = {
let index = window[idx].rabin_index.as_ref().unwrap();
delta::encode_rabin_with_index_and_max_size(
index,
&entry.data,
best_delta_size,
)
};
if let Some(delta_data) = delta {
let delta_size = delta_data.len();
if delta_size < best_delta_size {
best_delta_size = delta_size;
best_rate = 1.0 - delta_size as f64 / entry.data.len() as f64;
best_idx = Some(idx);
}
}
}
best_base = best_idx.map(|i| &window[i]);
}
}
#[cfg(not(feature = "diff_rabin"))]
{
let candidates: Vec<_> = window
.par_iter()
.with_min_len(3)
.filter_map(|try_base| {
if try_base.entry.obj_type != entry.obj_type
|| try_base.entry.chain_len >= MAX_CHAIN_LEN
|| try_base.entry.hash == entry.hash
{
return None;
}
let sym_ratio = (try_base.entry.data.len().min(entry.data.len()) as f64)
/ (try_base.entry.data.len().max(entry.data.len()) as f64);
let no_prefilter = disable_prefilter;
if sym_ratio < 0.5
|| (!no_prefilter && !cheap_similar(&try_base.entry.data, &entry.data))
{
return None;
}
let rate = if (try_base.entry.data.len() + entry.data.len()) / 2 > 64 {
delta::heuristic_encode_rate_parallel(&try_base.entry.data, &entry.data)
} else {
delta::encode_rate(&try_base.entry.data, &entry.data)
};
if rate > MIN_DELTA_RATE {
Some((rate, try_base))
} else {
None
}
})
.collect();
let tie_epsilon: f64 = 0.15;
for (rate, try_base) in candidates {
match best_base {
None => {
best_rate = rate;
best_base = Some(try_base);
}
Some(best_base_ref) => {
let is_better = if rate > best_rate + tie_epsilon {
true
} else if (rate - best_rate).abs() <= tie_epsilon {
try_base.entry.chain_len > best_base_ref.entry.chain_len
} else {
false
};
if is_better {
best_rate = rate;
best_base = Some(try_base);
}
}
}
}
}
if best_rate < MIN_DELTA_RATE {
best_base = None;
}
let mut entry_for_window = entry.clone();
let offset = best_base.map(|best_base| {
let delta = if enable_zstdelta {
entry.obj_type = ObjectType::OffsetZstdelta;
zstdelta::diff(&best_base.entry.data, &entry.data)
.map_err(|e| {
GitError::DeltaObjectError(format!("zstdelta diff failed: {e}"))
})
.unwrap()
} else {
#[cfg(feature = "diff_rabin")]
if enable_rabin {
entry.obj_type = ObjectType::OffsetDelta;
delta::encode_rabin_with_index(
best_base.rabin_index.as_ref().unwrap(),
&entry.data,
)
} else {
entry.obj_type = ObjectType::OffsetDelta;
delta::encode(&best_base.entry.data, &entry.data)
}
#[cfg(not(feature = "diff_rabin"))]
{
entry.obj_type = ObjectType::OffsetDelta;
delta::encode(&best_base.entry.data, &entry.data)
}
};
entry.data = delta;
entry.chain_len = best_base.entry.chain_len + 1;
current_offset - best_base.offset
});
entry_for_window.chain_len = entry.chain_len;
let obj_data = encode_one_object(entry, offset)?;
window.push_back(DeltaWindowEntry::new(entry_for_window, current_offset));
if window.len() > window_size {
window.pop_front();
}
let obj_data_len = obj_data.len();
res.push((obj_data, IndexEntry::new(entry, 0)));
current_offset += obj_data_len;
}
Ok(res)
}
}