#![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 (mut low, mut high): (usize, usize) = (0, node.subtrees.len());
while low < high {
let middle: usize = low + (high - low) / 2;
let (candidate, subtree) = &node.subtrees[middle];
match (candidate.len(), candidate.as_slice()).cmp(&(name.len(), name)) {
std::cmp::Ordering::Less => low = middle + 1,
std::cmp::Ordering::Greater => high = middle,
std::cmp::Ordering::Equal => return Some(subtree),
}
}
return None;
}
fn below(paths: &[&[u8]], range: (usize, usize), prefix: &[u8]) -> (usize, usize) {
let window: &[&[u8]] = &paths[range.0..range.1];
let (mut low, mut high): (usize, usize) = (0, window.len());
while low < high {
let middle: usize = low + (high - low) / 2;
if window[middle] < prefix {
low = middle + 1;
} else {
high = middle;
}
}
let start: usize = low;
let rest: &[&[u8]] = &window[start..];
let (mut inside, mut past): (usize, usize) = (0, rest.len());
while inside < past {
let middle: usize = inside + (past - inside) / 2;
if rest[middle].starts_with(prefix) {
inside = middle + 1;
} else {
past = middle;
}
}
let length: usize = inside;
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;
}
let valid_node: Option<&CacheTree> = match node {
Some(candidate) if candidate.entry_count >= 0 && candidate.id.as_slice() == tree => Some(candidate),
_ => None,
};
if let Some(valid) = valid_node {
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> = match node {
Some(parent) => subtree_of(parent, entry.name.as_slice()),
None => None,
};
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 mut paths: Vec<&[u8]> = Vec::new();
for entry in index {
paths.push(entry.path.as_slice());
}
let parts: UnequalParts = unequal_parts(database, tree, format, cache, paths.as_slice())?;
let mut kept: Vec<FlatEntry> = Vec::new();
for entry in outside(index, parts.skipped.as_slice()) {
kept.push(entry.clone());
}
return diff_sorted(parts.tree.as_slice(), kept.as_slice());
}
#[cfg(test)]
#[path = "repo_read_tree_cached_tests.rs"]
mod tests;