Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/gunnar-bitmap/src/reachability.rs

//! The reachability half: `want`/`have` as set arithmetic.
//!
//! This is the point of the whole crate. Answering a fetch normally means
//! walking the commit graph from every `want`, then walking again from every
//! `have` to subtract. With a bitmap index it is:
//!
//! ```text
//! send = (bitmap(w1) | bitmap(w2) | ...) & !(bitmap(h1) | bitmap(h2) | ...)
//! ```
//!
//! which touches each commit's bitmap once and never reads an object.
//!
//! # Which positions go in, which come out
//!
//! `wants` and `haves` are commit positions in the **`.idx`** — the same space
//! as [`crate::Entry::commit_index_pos`]. The [`Selection::objects`] that comes
//! back is addressed by **pack** position. Converting the result needs
//! [`crate::PackOrder`]; see [`crate::file`] for why the format mixes the two.
//!
//! # What this module deliberately does not do
//!
//! git falls back to a partial graph walk when a `want` has no stored bitmap:
//! it walks down from that commit until it reaches commits that *do* have one,
//! adding the objects it passes by hand. That fallback needs an object database,
//! and an object database is precisely the dependency that would stop this crate
//! from being liftable. So [`Reachability::objects_to_send`] reports the
//! positions it could not satisfy instead of guessing, and the caller — who has
//! the odb — completes them.
//!
//! Silently returning the bitmapped part would be the dangerous shape: a
//! *smaller* object set that looks like a successful answer, and a fetch that
//! ships a pack the client cannot use.

use crate::{Bitmap, Index};

/// Set operations over a parsed bitmap index.
#[derive(Clone, Copy, Debug)]
pub struct Reachability<'a> {
    index: &'a Index,
}

/// What a `want`/`have` query produced.
#[derive(Clone, Debug)]
pub struct Selection {
    /// Objects to send: reachable from the wants, not from the haves.
    pub objects: Bitmap,
    /// Want positions with no stored bitmap. Empty means the answer is complete.
    pub unsatisfied_wants: Vec<u32>,
    /// Have positions with no stored bitmap.
    ///
    /// These are the dangerous ones in the other direction: an unsubtracted
    /// `have` means sending objects the client already has. That is waste, not
    /// corruption, so it is reported rather than refused.
    pub unsatisfied_haves: Vec<u32>,
}

impl Selection {
    /// Whether every want and have was answered from the index alone.
    pub fn is_complete(&self) -> bool {
        self.unsatisfied_wants.is_empty() && self.unsatisfied_haves.is_empty()
    }
}

impl<'a> Reachability<'a> {
    /// Borrow an index for querying.
    pub fn new(index: &'a Index) -> Self {
        Reachability { index }
    }

    /// Union of the bitmaps of the given `.idx` commit positions, plus the ones
    /// that had no stored bitmap.
    pub fn union(&self, positions: impl IntoIterator<Item = u32>) -> (Bitmap, Vec<u32>) {
        let mut acc = Bitmap::new();
        let mut missing = Vec::new();
        for pos in positions {
            match self.index.bitmap_for_commit(pos) {
                Some(b) => acc.or_with(&b),
                None => missing.push(pos),
            }
        }
        (acc, missing)
    }

    /// `wants` minus `haves`, both given as `.idx` positions of commits.
    ///
    /// The resulting bitmap is addressed by pack position.
    pub fn objects_to_send(
        &self,
        wants: impl IntoIterator<Item = u32>,
        haves: impl IntoIterator<Item = u32>,
    ) -> Selection {
        let (mut objects, unsatisfied_wants) = self.union(wants);
        let (excluded, unsatisfied_haves) = self.union(haves);
        objects.and_not_with(&excluded);
        Selection {
            objects,
            unsatisfied_wants,
            unsatisfied_haves,
        }
    }

    /// Restrict a set to one object type, using the index's type bitmaps.
    ///
    /// This is how a pack writer learns which of the selected positions are
    /// commits, trees, blobs or tags without reading a single object header.
    pub fn of_type(&self, set: &Bitmap, kind: crate::kind::Kind) -> Bitmap {
        let mut out = set.clone();
        let mut keep = Bitmap::new();
        keep.or_ewah(self.index.type_bitmap(kind));
        out.and_with(&keep);
        out
    }

    /// Count the selection per object type: commits, trees, blobs, tags.
    pub fn counts_by_type(&self, set: &Bitmap) -> [u64; 4] {
        use crate::kind::Kind::*;
        [Commit, Tree, Blob, Tag].map(|k| self.of_type(set, k).count_ones())
    }
}