weavatrix-git 0.3.1

Fast, bounded, evidence-carrying Git reader with an optional read-only MCP server
Documentation
use std::{
    cmp::Ordering,
    collections::{BinaryHeap, HashSet},
};

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

#[derive(Clone, Debug, Eq, PartialEq)]
struct Pending {
    time: i64,
    sequence: u64,
    id: ObjectId,
    parents: Vec<ObjectId>,
}

impl Ord for Pending {
    fn cmp(&self, other: &Self) -> Ordering {
        (self.time, self.sequence, self.id).cmp(&(other.time, other.sequence, other.id))
    }
}

impl PartialOrd for Pending {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

pub struct RevWalk<'repo> {
    repository: &'repo Repository,
    pending: BinaryHeap<Pending>,
    scheduled: HashSet<ObjectId>,
    hidden: HashSet<ObjectId>,
    first_parent: bool,
    sequence: u64,
    emitted: usize,
    failed: bool,
}

impl<'repo> RevWalk<'repo> {
    pub(crate) fn new(repository: &'repo Repository) -> Self {
        Self {
            repository,
            pending: BinaryHeap::new(),
            scheduled: HashSet::new(),
            hidden: HashSet::new(),
            first_parent: false,
            sequence: 0,
            emitted: 0,
            failed: false,
        }
    }

    pub fn push(&mut self, id: ObjectId) -> Result<&mut Self> {
        self.schedule(id)?;
        Ok(self)
    }

    pub fn push_head(&mut self) -> Result<&mut Self> {
        let id = self
            .repository
            .head()?
            .target
            .ok_or_else(|| crate::GitError::NotFound("unborn HEAD".to_owned()))?;
        self.push(id)
    }

    pub fn push_ref(&mut self, name: &str) -> Result<&mut Self> {
        self.push(self.repository.resolve(name)?)
    }

    pub fn hide(&mut self, id: ObjectId) -> Result<&mut Self> {
        let mut stack = vec![id];
        while let Some(id) = stack.pop() {
            if !self.hidden.insert(id) {
                continue;
            }
            if self.hidden.len() > self.repository.limits().max_history_commits {
                return Err(GitError::LimitExceeded {
                    resource: "revwalk hidden commits",
                    limit: self.repository.limits().max_history_commits,
                });
            }
            stack.extend(self.repository.commit_metadata(id)?.parents);
        }
        Ok(self)
    }

    pub fn hide_ref(&mut self, name: &str) -> Result<&mut Self> {
        self.hide(self.repository.resolve(name)?)
    }

    pub const fn first_parent(&mut self, enabled: bool) -> &mut Self {
        self.first_parent = enabled;
        self
    }

    pub fn reset(&mut self) {
        self.pending.clear();
        self.scheduled.clear();
        self.hidden.clear();
        self.sequence = 0;
        self.emitted = 0;
        self.failed = false;
    }

    fn schedule(&mut self, id: ObjectId) -> Result<()> {
        if !self.scheduled.insert(id) {
            return Ok(());
        }
        if self.scheduled.len() > self.repository.limits().max_history_commits {
            return Err(GitError::LimitExceeded {
                resource: "revwalk commits",
                limit: self.repository.limits().max_history_commits,
            });
        }
        let commit = self.repository.commit_metadata(id)?;
        self.pending.push(Pending {
            time: commit.committer_time,
            sequence: self.sequence,
            id,
            parents: commit.parents,
        });
        self.sequence = self.sequence.wrapping_add(1);
        Ok(())
    }
}

impl Iterator for RevWalk<'_> {
    type Item = Result<ObjectId>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.failed || self.emitted >= self.repository.limits().max_history_commits {
            return None;
        }
        while let Some(item) = self.pending.pop() {
            if self.hidden.contains(&item.id) {
                continue;
            }
            let parents = if self.first_parent {
                item.parents.get(..1).unwrap_or(&[])
            } else {
                &item.parents
            };
            for parent in parents {
                if let Err(error) = self.schedule(*parent) {
                    self.failed = true;
                    return Some(Err(error));
                }
            }
            self.emitted += 1;
            return Some(Ok(item.id));
        }
        None
    }
}

impl Repository {
    #[must_use]
    pub fn revwalk(&self) -> RevWalk<'_> {
        RevWalk::new(self)
    }
}