use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use super::merge_trees_walk::PathPrefixIndex;
use crate::objects::{parse_tree, ObjectId, ObjectKind, TreeEntry};
use crate::odb::Odb;
const MODE_DIR: u32 = 0o040000;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum MergeTreeSide {
Base,
Ours,
Theirs,
}
#[derive(Default)]
pub(crate) struct MergeTreeCache {
parsed: HashMap<ObjectId, Rc<[TreeEntry]>>,
dir_with_descendant: [HashSet<Vec<u8>>; 3],
}
impl MergeTreeCache {
pub(crate) fn clear_retained(&mut self) {
self.parsed.clear();
self.dir_with_descendant = [HashSet::new(), HashSet::new(), HashSet::new()];
}
pub(crate) fn tree_entries(
&mut self,
odb: &Odb,
oid: &ObjectId,
) -> crate::error::Result<Rc<[TreeEntry]>> {
if let Some(entries) = self.parsed.get(oid) {
return Ok(Rc::clone(entries));
}
let obj = odb.read(oid)?;
let entries: Rc<[TreeEntry]> = if obj.kind == ObjectKind::Tree {
parse_tree(&obj.data)?.into()
} else {
Rc::from(Vec::new())
};
self.parsed.insert(*oid, Rc::clone(&entries));
Ok(entries)
}
pub(crate) fn note_file_path(&mut self, side: MergeTreeSide, path: &[u8]) {
let idx = side as usize;
for (i, &b) in path.iter().enumerate() {
if b == b'/' {
self.dir_with_descendant[idx].insert(path[..i].to_vec());
}
}
}
pub(crate) fn dir_has_descendant(&self, side: MergeTreeSide, prefix: &[u8]) -> bool {
self.dir_with_descendant[side as usize].contains(prefix)
}
}
pub(crate) fn find_tree_entry<'a>(entries: &'a [TreeEntry], name: &[u8]) -> Option<&'a TreeEntry> {
entries.iter().find(|e| e.name.as_slice() == name)
}
fn tree_entry_to_index(path: &[u8], te: &TreeEntry) -> crate::index::IndexEntry {
crate::index::IndexEntry {
ctime_sec: 0,
ctime_nsec: 0,
mtime_sec: 0,
mtime_nsec: 0,
dev: 0,
ino: 0,
mode: te.mode,
uid: 0,
gid: 0,
size: 0,
oid: te.oid,
flags: path.len().min(0xFFF) as u16,
flags_extended: None,
path: path.to_vec(),
base_index_pos: 0,
}
}
pub(crate) fn lookup_index_entry_at_path_cached(
cache: &mut MergeTreeCache,
odb: &Odb,
tree_oid: &ObjectId,
path: &[u8],
) -> crate::error::Result<Option<crate::index::IndexEntry>> {
if path.is_empty() {
return Ok(None);
}
let parts: Vec<&[u8]> = path
.split(|&b| b == b'/')
.filter(|s| !s.is_empty())
.collect();
let mut current = *tree_oid;
for (i, comp) in parts.iter().enumerate() {
let entries = cache.tree_entries(odb, ¤t)?;
let Some(te) = find_tree_entry(&entries, comp) else {
return Ok(None);
};
let is_last = i + 1 == parts.len();
if is_last {
return Ok(Some(tree_entry_to_index(path, te)));
}
if te.mode != MODE_DIR {
return Ok(None);
}
current = te.oid;
}
Ok(None)
}
pub(crate) fn tree_has_entries_with_path_prefix_cached(
cache: &mut MergeTreeCache,
odb: &Odb,
tree_oid: &ObjectId,
side: MergeTreeSide,
prefix: &[u8],
) -> crate::error::Result<bool> {
if cache.dir_has_descendant(side, prefix) {
return Ok(true);
}
fn walk(
cache: &mut MergeTreeCache,
odb: &Odb,
tree_oid: &ObjectId,
rest: &[u8],
) -> crate::error::Result<bool> {
let entries = cache.tree_entries(odb, tree_oid)?;
let mut descend: Vec<(ObjectId, usize)> = Vec::new();
for te in entries.iter() {
let name = te.name.as_slice();
if name.len() > rest.len() && name.starts_with(rest) && name[rest.len()] == b'/' {
return Ok(true);
}
if te.mode != MODE_DIR {
continue;
}
if name == rest {
descend.push((te.oid, rest.len()));
} else if rest.len() > name.len() && rest.starts_with(name) && rest[name.len()] == b'/'
{
descend.push((te.oid, name.len() + 1));
}
}
drop(entries);
for (oid, consumed) in descend {
if consumed == rest.len() {
if !cache.tree_entries(odb, &oid)?.is_empty() {
return Ok(true);
}
} else if walk(cache, odb, &oid, &rest[consumed..])? {
return Ok(true);
}
}
Ok(false)
}
if prefix.is_empty() {
return Ok(false);
}
walk(cache, odb, tree_oid, prefix)
}
pub(crate) fn side_tree_has_descendant_at_cached(
cache: &mut MergeTreeCache,
odb: &Odb,
tree_oid: &ObjectId,
side: MergeTreeSide,
prefix: &[u8],
sparse_paths: &PathPrefixIndex,
) -> crate::error::Result<bool> {
if sparse_paths.has_descendant(prefix) {
return Ok(true);
}
if cache.dir_has_descendant(side, prefix) {
return Ok(true);
}
if let Some(entry) = lookup_index_entry_at_path_cached(cache, odb, tree_oid, prefix)? {
if entry.mode == MODE_DIR {
let children = cache.tree_entries(odb, &entry.oid)?;
if !children.is_empty() {
return Ok(true);
}
}
}
tree_has_entries_with_path_prefix_cached(cache, odb, tree_oid, side, prefix)
}