//! blob Use case: implementation of `BlobOps` for `GitRepository`. use gix::hash::ObjectId; use crate::GitRepository; use crate::blob::impls::BlobOps; use crate::blob::types::{BlobContent, BlobMeta, LfsPointer, RawDownload}; use crate::validate; const LFS_POINTER_PREFIX: &[u8] = b"version https://git-lfs.github.com/spec/v1\n"; fn is_binary(data: &[u8]) -> bool { let limit = data.len().min(8000); data[..limit].contains(&0) } fn parse_lfs_pointer(data: &[u8]) -> Option { if !data.starts_with(LFS_POINTER_PREFIX) { return None; } let text = std::str::from_utf8(data).ok()?; let mut oid: Option = None; let mut size: Option = None; for line in text.lines() { let line = line.trim_end(); if let Some(rest) = line.strip_prefix("oid sha256:") { oid = Some(rest.to_string()); } else if let Some(rest) = line.strip_prefix("size ") { size = rest.parse().ok(); } } Some(LfsPointer { version: "https://git-lfs.github.com/spec/v1".to_string(), oid: oid?, size: size?, }) } impl BlobOps for GitRepository { fn blob_content(&self, id: &ObjectId) -> Result { if let Some(hit) = self.cache_get_oid::("blob", id) { validate::enforce_max_size(&hit.data, validate::MAX_BLOB_SIZE, "blob")?; return Ok(hit); } let repo = self.repo()?; let blob = repo .find_blob(*id) .map_err(|e| crate::Error::Find(e.to_string()))?; let data = blob.data.clone(); validate::enforce_max_size(&data, validate::MAX_BLOB_SIZE, "blob")?; let size = data.len() as i64; let bin = is_binary(&data); let v = BlobContent { id: *id, data, size, is_binary: bin, }; self.cache_put_oid("blob", id, &v); Ok(v) } fn blob_metadata(&self, id: &ObjectId) -> Result { if let Some(hit) = self.cache_get_oid::("blob_meta", id) { return Ok(hit); } let repo = self.repo()?; let obj = repo .find_object(*id) .map_err(|e| crate::Error::Find(e.to_string()))?; let data = &obj.data; let size = obj.data.len() as i64; let lfs = parse_lfs_pointer(data).is_some(); let v = BlobMeta { id: *id, size, is_binary: is_binary(data), is_lfs_pointer: lfs, lfs: if lfs { parse_lfs_pointer(data) } else { None }, object_type: obj.kind.into(), }; self.cache_put_oid("blob_meta", id, &v); Ok(v) } fn blob_is_lfs_pointer(&self, data: &[u8]) -> Result, crate::Error> { Ok(parse_lfs_pointer(data)) } fn blob_raw_download(&self, id: &ObjectId) -> Result { if let Some(hit) = self.cache_get_oid::("blob_raw", id) { return Ok(hit); } let repo = self.repo()?; let obj = repo .find_object(*id) .map_err(|e| crate::Error::Find(e.to_string()))?; let data = &obj.data; let v = RawDownload { id: *id, size: obj.data.len() as i64, is_lfs_pointer: parse_lfs_pointer(data).is_some(), }; self.cache_put_oid("blob_raw", id, &v); Ok(v) } }