weavatrix-git 0.2.0

Dependency-free, evidence-carrying Git repository reader
Documentation
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(())
}