use alloc::string::String;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObjectFilter {
BlobNone,
BlobLimit(u64),
TreeNone,
None,
}
impl ObjectFilter {
pub fn parse(spec: &str) -> Self {
let spec = spec.trim();
if spec == "blob:none" {
return ObjectFilter::BlobNone;
}
if spec == "tree:0" {
return ObjectFilter::TreeNone;
}
if let Some(limit) = spec.strip_prefix("blob:limit=")
&& let Some(bytes) = parse_size(limit)
{
return ObjectFilter::BlobLimit(bytes);
}
ObjectFilter::None
}
pub fn keeps_blob(&self, size: usize) -> bool {
match self {
ObjectFilter::BlobNone | ObjectFilter::TreeNone => false,
ObjectFilter::BlobLimit(limit) => (size as u64) < *limit,
ObjectFilter::None => true,
}
}
pub fn keeps(&self, ty: crate::object::ObjectType, size: usize) -> bool {
use crate::object::ObjectType;
match ty {
ObjectType::Blob => self.keeps_blob(size),
ObjectType::Tree => !matches!(self, ObjectFilter::TreeNone),
ObjectType::Commit | ObjectType::Tag => true,
}
}
pub fn is_active(&self) -> bool {
!matches!(self, ObjectFilter::None)
}
}
fn parse_size(text: &str) -> Option<u64> {
let text = text.trim();
let (digits, mult) = match text.chars().last() {
Some('k') | Some('K') => (&text[..text.len() - 1], 1024),
Some('m') | Some('M') => (&text[..text.len() - 1], 1024 * 1024),
Some('g') | Some('G') => (&text[..text.len() - 1], 1024 * 1024 * 1024),
_ => (text, 1),
};
digits.parse::<u64>().ok().map(|n| n * mult)
}
pub fn from_spec(spec: Option<&str>) -> ObjectFilter {
spec.map(ObjectFilter::parse).unwrap_or(ObjectFilter::None)
}
pub fn to_spec(filter: &ObjectFilter) -> Option<String> {
match filter {
ObjectFilter::BlobNone => Some("blob:none".into()),
ObjectFilter::BlobLimit(n) => Some(alloc::format!("blob:limit={n}")),
ObjectFilter::TreeNone => Some("tree:0".into()),
ObjectFilter::None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_and_applies_filters() {
assert_eq!(ObjectFilter::parse("blob:none"), ObjectFilter::BlobNone);
assert!(!ObjectFilter::BlobNone.keeps_blob(0));
assert!(!ObjectFilter::BlobNone.keeps_blob(1000));
assert_eq!(
ObjectFilter::parse("blob:limit=1k"),
ObjectFilter::BlobLimit(1024)
);
let f = ObjectFilter::parse("blob:limit=100");
assert!(f.keeps_blob(99)); assert!(!f.keeps_blob(100)); assert!(!f.keeps_blob(500));
use crate::object::ObjectType;
let t = ObjectFilter::parse("tree:0");
assert_eq!(t, ObjectFilter::TreeNone);
assert!(!t.keeps(ObjectType::Tree, 10) && !t.keeps(ObjectType::Blob, 0));
assert!(t.keeps(ObjectType::Commit, 10) && t.keeps(ObjectType::Tag, 10));
assert!(ObjectFilter::BlobNone.keeps(ObjectType::Tree, 10));
assert_eq!(ObjectFilter::parse("tree:1"), ObjectFilter::None);
assert!(ObjectFilter::None.keeps_blob(usize::MAX));
assert!(!ObjectFilter::None.is_active());
}
#[test]
fn round_trips_a_spec() {
for spec in ["blob:none", "blob:limit=2048", "tree:0"] {
let f = ObjectFilter::parse(spec);
assert_eq!(to_spec(&f).as_deref(), Some(spec));
}
}
}