#![cfg(unix)]
use crate::index_cache_tree::CacheTree;
use crate::repo_read_diff::{Change, diff_sorted};
use crate::repo_read_layout::ObjectFormat;
use crate::repo_read_object::{ObjectKind, name_length};
use crate::repo_read_odb::ObjectDatabase;
use crate::repo_read_tree::{FlatEntry, is_tree_mode, parse_tree};
const TREE_DEPTH_LIMIT: usize = 256;
const FLAT_ENTRY_LIMIT: usize = 4_000_000;
#[derive(Debug)]
pub struct UnequalParts {
pub tree: Vec<FlatEntry>,
pub skipped: Vec<(usize, usize)>,
}
struct Walk<'a> {
database: &'a ObjectDatabase,
format: ObjectFormat,
paths: &'a [&'a [u8]],
tree: Vec<FlatEntry>,
skipped: Vec<(usize, usize)>,
}
fn subtree_of<'a>(node: &'a CacheTree, name: &[u8]) -> Option<&'a CacheTree> {
let at: usize = node.subtrees.binary_search_by(|(candidate, _)| { return (candidate.len(), candidate.as_slice()).cmp(&(name.len(), name)); }).ok()?;
return Some(&node.subtrees[at].1);
}
fn below(paths: &[&[u8]], range: (usize, usize), prefix: &[u8]) -> (usize, usize) {
let window: &[&[u8]] = &paths[range.0..range.1];
let start: usize = window.partition_point(|path| { return *path < prefix; });
let length: usize = window[start..].partition_point(|path| { return path.starts_with(prefix); });
return (range.0 + start, range.0 + start + length);
}
impl Walk<'_> {
fn directory(&mut self, tree: &[u8], node: Option<&CacheTree>, prefix: &mut Vec<u8>, range: (usize, usize), depth: usize) -> Option<()> {
if depth > TREE_DEPTH_LIMIT {
return None;
}
if let Some(valid) = node.filter(|candidate| { return candidate.entry_count >= 0 && candidate.id.as_slice() == tree; }) {
if usize::try_from(valid.entry_count).ok()? != range.1 - range.0 {
return None;
}
self.skipped.push(range);
return Some(());
}
let object = self.database.read(tree)?;
if object.kind != ObjectKind::Tree {
return None;
}
for entry in parse_tree(object.bytes.as_slice(), name_length(self.format))? {
let length: usize = prefix.len();
prefix.extend_from_slice(entry.name.as_slice());
if is_tree_mode(entry.mode) {
prefix.push(b'/');
let inner: (usize, usize) = below(self.paths, range, prefix.as_slice());
let child: Option<&CacheTree> = node.and_then(|parent| { return subtree_of(parent, entry.name.as_slice()); });
self.directory(entry.id.as_slice(), child, prefix, inner, depth + 1)?;
} else {
self.tree.push(FlatEntry { path: prefix.clone(), mode: entry.mode, id: entry.id });
if self.tree.len() > FLAT_ENTRY_LIMIT {
return None;
}
}
prefix.truncate(length);
}
return Some(());
}
}
pub fn unequal_parts(database: &ObjectDatabase, tree: &[u8], format: ObjectFormat, cache: &CacheTree, paths: &[&[u8]]) -> Option<UnequalParts> {
let root = database.read(tree)?;
if root.kind != ObjectKind::Tree {
return None;
}
let mut walk: Walk<'_> = Walk { database, format, paths, tree: Vec::new(), skipped: Vec::new() };
walk.directory(tree, Some(cache), &mut Vec::new(), (0, paths.len()), 0)?;
walk.skipped.sort_unstable();
return Some(UnequalParts { tree: walk.tree, skipped: walk.skipped });
}
pub fn outside<'a, Item>(all: &'a [Item], skipped: &[(usize, usize)]) -> Vec<&'a Item> {
let mut kept: Vec<&Item> = Vec::new();
let mut at: usize = 0;
for (start, end) in skipped {
kept.extend(all[at..*start].iter());
at = *end;
}
kept.extend(all[at..].iter());
return kept;
}
pub fn changes_with_cache(database: &ObjectDatabase, tree: &[u8], format: ObjectFormat, cache: &CacheTree, index: &[FlatEntry]) -> Option<Vec<Change>> {
let paths: Vec<&[u8]> = index.iter().map(|entry| { return entry.path.as_slice(); }).collect();
let parts: UnequalParts = unequal_parts(database, tree, format, cache, paths.as_slice())?;
let kept: Vec<FlatEntry> = outside(index, parts.skipped.as_slice()).into_iter().cloned().collect();
return diff_sorted(parts.tree.as_slice(), kept.as_slice());
}
#[cfg(test)]
#[path = "repo_read_tree_cached_tests.rs"]
mod tests;