weavatrix-git 0.3.1

Fast, bounded, evidence-carrying Git reader with an optional read-only MCP server
Documentation
use std::collections::BTreeMap;

use crate::{EntryKind, GitError, ObjectId, Repository, Result};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SnapshotEntry {
    pub path: Vec<u8>,
    pub mode: u32,
    pub id: ObjectId,
    pub kind: EntryKind,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommitSnapshot {
    pub commit: ObjectId,
    pub tree: ObjectId,
    pub entries: Vec<SnapshotEntry>,
}

impl Repository {
    pub fn snapshot(&self, revision: &str) -> Result<CommitSnapshot> {
        at(self, revision)
    }

    pub fn tree_manifest(&self, tree: ObjectId) -> Result<Vec<SnapshotEntry>> {
        manifest(self, tree)
    }
}

pub(crate) fn at(repository: &Repository, revision: &str) -> Result<CommitSnapshot> {
    let commit = repository.resolve(revision)?;
    let tree = repository.commit_metadata(commit)?.tree;
    Ok(CommitSnapshot {
        commit,
        tree,
        entries: manifest(repository, tree)?,
    })
}

pub(crate) fn manifest(repository: &Repository, tree: ObjectId) -> Result<Vec<SnapshotEntry>> {
    let mut entries = BTreeMap::new();
    flatten(repository, tree, &[], 0, &mut entries)?;
    Ok(entries.into_values().collect())
}

fn flatten(
    repository: &Repository,
    tree: ObjectId,
    prefix: &[u8],
    depth: usize,
    output: &mut BTreeMap<Vec<u8>, SnapshotEntry>,
) -> Result<()> {
    if depth >= repository.limits().max_tree_depth {
        return Err(GitError::LimitExceeded {
            resource: "tree depth",
            limit: repository.limits().max_tree_depth,
        });
    }
    for entry in repository.tree(tree)?.entries {
        let path = join(prefix, &entry.name);
        if entry.kind == EntryKind::Tree {
            flatten(repository, entry.id, &path, depth + 1, output)?;
            continue;
        }
        if output.len() >= repository.limits().max_tree_entries {
            return Err(GitError::LimitExceeded {
                resource: "snapshot entries",
                limit: repository.limits().max_tree_entries,
            });
        }
        output.insert(
            path.clone(),
            SnapshotEntry {
                path,
                mode: entry.mode,
                id: entry.id,
                kind: entry.kind,
            },
        );
    }
    Ok(())
}

fn join(prefix: &[u8], name: &[u8]) -> Vec<u8> {
    let mut path = Vec::with_capacity(prefix.len() + name.len() + usize::from(!prefix.is_empty()));
    if !prefix.is_empty() {
        path.extend_from_slice(prefix);
        path.push(b'/');
    }
    path.extend_from_slice(name);
    path
}