use gix::object::tree::EntryKind; use crate::bare::GitBare; use crate::error::{GitError, GitResult}; use crate::paginate; use crate::pb::{ListTreeRequest, ListTreeResponse, TreeEntry, object_selector, tree_entry}; impl GitBare { pub fn list_tree(&self, request: ListTreeRequest) -> GitResult { let repo = self.gix_repo()?; let revision = match request.revision.clone().and_then(|s| s.selector) { Some(object_selector::Selector::Oid(oid)) => oid.hex, Some(object_selector::Selector::Revision(name)) => name.revision, None => "HEAD".into(), }; let mut tree = repo .rev_parse_single(format!("{}^{{tree}}", revision).as_str())? .object()? .try_into_tree() .map_err(|e| GitError::Gix(e.to_string()))?; if !request.path.is_empty() { let entry = tree .lookup_entry_by_path(&request.path)? .ok_or_else(|| GitError::NotFound(request.path.clone()))?; tree = entry .object()? .try_into_tree() .map_err(|e| GitError::Gix(e.to_string()))?; } let base = request.path.trim_matches('/').to_string(); let mut entries = Vec::new(); for entry in tree.iter() { let entry = entry?; let name = String::from_utf8_lossy(entry.filename()).into_owned(); let path = if base.is_empty() { name.clone() } else { format!("{base}/{name}") }; let kind = entry.kind(); let hex = entry.id().to_string(); entries.push(TreeEntry { name, path: path.clone(), oid: Some(self.oid_to_pb(hex)), r#type: entry_type(kind) as i32, mode: u32::from_str_radix(&format!("{:o}", entry.mode()), 8).unwrap_or(0), size: entry_size(&repo, entry.id().to_string().as_str()).unwrap_or(0), }); if request.recursive && matches!(kind, EntryKind::Tree) { let child = self.list_tree(ListTreeRequest { repository: request.repository.clone(), revision: request.revision.clone(), path, recursive: true, pagination: None, })?; entries.extend(child.entries); } } let (entries, page_info) = paginate::paginate(&entries, request.pagination.as_ref()); Ok(ListTreeResponse { entries, page_info: Some(page_info), truncated: false, }) } } fn entry_type(kind: EntryKind) -> tree_entry::EntryType { match kind { EntryKind::Tree => tree_entry::EntryType::TreeEntryTypeTree, EntryKind::Blob => tree_entry::EntryType::TreeEntryTypeBlob, EntryKind::BlobExecutable => tree_entry::EntryType::TreeEntryTypeExecutable, EntryKind::Link => tree_entry::EntryType::TreeEntryTypeSymlink, EntryKind::Commit => tree_entry::EntryType::TreeEntryTypeCommit, } } fn entry_size(repo: &gix::Repository, oid: &str) -> Option { let id = gix::hash::ObjectId::from_hex(oid.as_bytes()).ok()?; let object = repo.find_object(id).ok()?; object.data.len().try_into().ok() }