use crate::{
ObjectId, ObjectKind, ObjectReadError, ReadLimits, TreeCompareError, TreeCompareLimits,
};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum Copies {
#[default]
Disabled,
Modified,
ModifiedPostimage,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct Options {
pub similarity: u8,
pub copies: Copies,
pub track_empty: bool,
pub candidate_limit: usize,
pub approximate_binary: bool,
}
impl Options {
#[must_use]
pub fn with_similarity(mut self, similarity: u8) -> Self {
self.similarity = similarity;
self
}
#[must_use]
pub fn with_copies(mut self, copies: Copies) -> Self {
self.copies = copies;
self
}
#[must_use]
pub fn with_track_empty(mut self, track_empty: bool) -> Self {
self.track_empty = track_empty;
self
}
#[must_use]
pub fn with_candidate_limit(mut self, candidate_limit: usize) -> Self {
self.candidate_limit = candidate_limit;
self
}
#[must_use]
pub fn with_approximate_binary(mut self, approximate_binary: bool) -> Self {
self.approximate_binary = approximate_binary;
self
}
}
impl Default for Options {
fn default() -> Self {
Self {
similarity: 50,
copies: Copies::Disabled,
track_empty: false,
candidate_limit: 1000,
approximate_binary: true,
}
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct Limits {
pub trees: TreeCompareLimits,
pub read: ReadLimits,
pub max_blob_bytes: usize,
pub max_spans: usize,
pub max_pairs: usize,
pub max_work: usize,
}
impl Limits {
#[must_use]
pub fn with_trees(mut self, trees: TreeCompareLimits) -> Self {
self.trees = trees;
self
}
#[must_use]
pub fn with_read(mut self, read: ReadLimits) -> Self {
self.read = read;
self
}
#[must_use]
pub fn with_max_blob_bytes(mut self, max_blob_bytes: usize) -> Self {
self.max_blob_bytes = max_blob_bytes;
self
}
#[must_use]
pub fn with_max_spans(mut self, max_spans: usize) -> Self {
self.max_spans = max_spans;
self
}
#[must_use]
pub fn with_max_pairs(mut self, max_pairs: usize) -> Self {
self.max_pairs = max_pairs;
self
}
#[must_use]
pub fn with_max_work(mut self, max_work: usize) -> Self {
self.max_work = max_work;
self
}
}
impl Default for Limits {
fn default() -> Self {
Self {
trees: TreeCompareLimits::default(),
read: ReadLimits::default(),
max_blob_bytes: 64 * 1024 * 1024,
max_spans: 1_000_000,
max_pairs: 1_000_000,
max_work: 100_000_000,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Kind {
Rename,
Copy,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct Rewrite {
pub source: Vec<u8>,
pub target: Vec<u8>,
pub source_id: ObjectId,
pub target_id: ObjectId,
pub similarity: u8,
pub kind: Kind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct Detection {
pub rewrites: Vec<Rewrite>,
pub inexact_skipped: Option<InexactSkipped>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct InexactSkipped {
pub sources: usize,
pub targets: usize,
pub limit: usize,
}
impl InexactSkipped {
#[must_use]
pub fn suggested_limit(&self) -> usize {
self.sources.max(self.targets)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("rewrite similarity must be in 1..=100, got {0}")]
Similarity(u8),
#[error("comparing rewrite trees")]
Trees(#[from] TreeCompareError),
#[error("reading rewrite blob {id}")]
Read {
id: ObjectId,
#[source]
source: ObjectReadError,
},
#[error("missing rewrite blob {0}")]
Missing(ObjectId),
#[error("rewrite object {id} is {actual:?}, not a blob")]
NotBlob {
id: ObjectId,
actual: ObjectKind,
},
#[error("rewrite candidates ({sources} sources, {targets} targets) exceed limit {limit}")]
Candidates {
sources: usize,
targets: usize,
limit: usize,
},
#[error("rewrite resource limit exhausted: {0}")]
Limit(&'static str),
#[error("rewrite detection cancelled")]
Cancelled,
}