gitdataai/lib/git/cmd/tree/tree_entry.rs
2026-05-30 01:38:40 +08:00

144 lines
3.9 KiB
Rust

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<bool> {
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<Vec<TreeEntry>> {
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<TreeEntry> {
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
}