use crate::{ bare::GitBare, cmd::{ oid::ObjectId, tree::{TreeEntry, TreeKind}, }, errors::{GitError, GitResult}, }; const LFS_POINTER_PREFIX: &[u8] = b"version https://git-lfs.github.com/spec/v"; const LFS_OID_MARKER: &[u8] = b"\noid sha256:"; const LFS_POINTER_MAX_SIZE: usize = 512; impl GitBare { fn blob_is_lfs_pointer(&self, id: ObjectId) -> GitResult { let repo = self.gix_repo()?; let gix_id: gix::hash::ObjectId = (&id).try_into()?; let blob = repo .find_blob(gix_id) .map_err(|_| GitError::ObjectNotFound(id.as_str().to_string()))?; if blob.data.len() > LFS_POINTER_MAX_SIZE { return Ok(false); } let data = &blob.data; Ok(data.starts_with(LFS_POINTER_PREFIX) && contains_subslice(data, LFS_OID_MARKER)) } pub fn tree_entries(&self, tree: ObjectId) -> GitResult> { let repo = self.gix_repo()?; let gix_id: gix::hash::ObjectId = (&tree).try_into()?; let gix_tree = repo.find_tree(gix_id)?; let mut entries = Vec::new(); for entry_result in gix_tree.iter() { let entry = entry_result?; let name = entry.inner.filename.to_string(); let oid = ObjectId::new(entry.inner.oid.to_hex().to_string()); let is_tree = entry.inner.mode.is_tree(); let is_lfs = if !is_tree { self.blob_is_lfs_pointer(oid.clone()).unwrap_or(false) } else { false }; let kind = if is_tree { TreeKind::Tree } else if is_lfs { TreeKind::LfsPointer } else { TreeKind::Blob }; let filemode = entry.inner.mode.value() as u32; let is_binary = match kind { TreeKind::LfsPointer => false, TreeKind::Blob => { self.blob_is_binary(oid.clone()).unwrap_or(false) } TreeKind::Tree => false, }; entries.push(TreeEntry { name, oid, kind, filemode, is_binary, is_lfs, }); } Ok(entries) } pub fn tree_entry_by_path( &self, tree: ObjectId, path: String, ) -> GitResult { let repo = self.gix_repo()?; let gix_id: gix::hash::ObjectId = (&tree).try_into()?; let gix_tree = repo.find_tree(gix_id)?; let entry = gix_tree.find_entry(path.as_str()).ok_or_else(|| { GitError::ObjectNotFound(format!( "path '{}' not found in tree {}", path, tree )) })?; let name = entry.inner.filename.to_string(); let oid = ObjectId::new(entry.inner.oid.to_hex().to_string()); let is_tree = entry.inner.mode.is_tree(); let is_lfs = if !is_tree { self.blob_is_lfs_pointer(oid.clone()).unwrap_or(false) } else { false }; let kind = if is_tree { TreeKind::Tree } else if is_lfs { TreeKind::LfsPointer } else { TreeKind::Blob }; let filemode = entry.inner.mode.value() as u32; let is_binary = match kind { TreeKind::LfsPointer => false, TreeKind::Blob => self.blob_is_binary(oid.clone()).unwrap_or(false), TreeKind::Tree => false, }; Ok(TreeEntry { name, oid, kind, filemode, is_binary, is_lfs, }) } } fn contains_subslice(data: &[u8], pattern: &[u8]) -> bool { if pattern.len() > data.len() { return false; } for window in data.windows(pattern.len()) { if window == pattern { return true; } } false }