use std::collections::BTreeMap;
use crate::{EntryKind, ObjectId, Repository, Result, TreeEntry};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ChangeKind {
Added,
Deleted,
Modified,
TypeChanged,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TreeChange {
pub path: Vec<u8>,
pub kind: ChangeKind,
pub old: Option<TreeEntry>,
pub new: Option<TreeEntry>,
}
pub(crate) fn between(
repository: &Repository,
old: ObjectId,
new: ObjectId,
) -> Result<Vec<TreeChange>> {
across(repository, old, repository, new)
}
pub(crate) fn across(
old_repository: &Repository,
old: ObjectId,
new_repository: &Repository,
new: ObjectId,
) -> Result<Vec<TreeChange>> {
if old == new {
return Ok(Vec::new());
}
let mut old_entries = BTreeMap::new();
let mut new_entries = BTreeMap::new();
flatten(old_repository, old, &[], 0, &mut old_entries)?;
flatten(new_repository, new, &[], 0, &mut new_entries)?;
let mut paths = old_entries
.keys()
.chain(new_entries.keys())
.collect::<Vec<_>>();
paths.sort_unstable();
paths.dedup();
let mut changes = Vec::new();
for path in paths {
let old_entry = old_entries.get(path);
let new_entry = new_entries.get(path);
let kind = match (old_entry, new_entry) {
(None, Some(_)) => ChangeKind::Added,
(Some(_), None) => ChangeKind::Deleted,
(Some(old), Some(new)) if old.kind != new.kind => ChangeKind::TypeChanged,
(Some(old), Some(new)) if old.id != new.id || old.mode != new.mode => {
ChangeKind::Modified
}
_ => continue,
};
changes.push(TreeChange {
path: path.clone(),
kind,
old: old_entry.cloned(),
new: new_entry.cloned(),
});
}
Ok(changes)
}
fn flatten(
repository: &Repository,
tree_id: ObjectId,
prefix: &[u8],
depth: usize,
output: &mut BTreeMap<Vec<u8>, TreeEntry>,
) -> Result<()> {
if depth >= repository.limits().max_tree_depth {
return Err(crate::GitError::LimitExceeded {
resource: "tree depth",
limit: repository.limits().max_tree_depth,
});
}
for mut entry in repository.tree(tree_id)?.entries {
let mut path = Vec::with_capacity(prefix.len() + entry.name.len() + 1);
if !prefix.is_empty() {
path.extend_from_slice(prefix);
path.push(b'/');
}
path.extend_from_slice(&entry.name);
if entry.kind == EntryKind::Tree {
flatten(repository, entry.id, &path, depth + 1, output)?;
} else {
entry.name.clone_from(&path);
output.insert(path, entry);
}
}
Ok(())
}