thin-fetch 0.1.0

Low-level Git crate to fetch objects with high granularity
Documentation
use crate::object::Object;
use crate::utils::as_hex;
use crate::{Error, Execute as _, Fetch, WithError as _, bail};

use super::Repo;

impl Repo {
    /// Fetch listed blobs for a given commit.
    ///
    /// # Errors
    ///
    /// Returns an error if the `fetch` command or parsing the response fails.
    pub fn fetch_blobs(&self, oid: &str, blobs: Vec<String>) -> Result<Vec<Object>, Error> {
        let cmd = Fetch {
            want: blobs,
            have: vec![oid.to_owned()],
            filter: Some("object:type=blob".to_owned()),
            deepen: None,
            hash_kind: self.hash_kind,
            include_tag: false,
        };

        let packfile = cmd.execute(self)?;

        packfile.collect::<Result<_, _>>()
    }

    /// Fetch commits between `have` and `want`, optionally including blobs.
    ///
    /// # Errors
    ///
    /// Returns an error if the `fetch` command or parsing the response fails.
    pub fn fetch_tree(&self, have: &str, want: &str, blobs: bool) -> Result<Vec<Object>, Error> {
        let cmd = Fetch {
            want: vec![want.to_owned()],
            have: vec![have.to_owned()],
            filter: if blobs {
                None
            } else {
                Some("blob:none".to_owned())
            },
            deepen: None,
            hash_kind: self.hash_kind,
            include_tag: false,
        };

        let packfile = cmd.execute(self)?;

        packfile.collect::<Result<_, _>>()
    }

    /// Fetch commits between `have` and `want`, parsing them into a linear history.
    ///
    /// # Errors
    ///
    /// Returns an error if the `fetch` command fails, cannot parse the response,
    /// or the history is not linear.
    pub fn fetch_history_linear(
        &mut self,
        start: &str,
        end: &str,
        blobs: bool,
    ) -> Result<Vec<String>, Error> {
        let cmd = Fetch {
            want: vec![end.to_owned()],
            have: vec![start.to_owned()],
            filter: if blobs {
                None
            } else {
                Some("blob:none".to_owned())
            },
            deepen: None,
            hash_kind: self.hash_kind,
            include_tag: false,
        };

        let packfile = cmd.execute(self)?;

        for obj in packfile {
            let obj = obj.with_err_msg("could not get object")?;
            let hash = as_hex(obj.hash().as_ref());

            self.object_cache.insert(hash, obj);
        }

        let mut new_head = end.to_owned();
        let mut commits = vec![];

        loop {
            let Some(Object::Commit(commit)) = self.object_cache.get(&new_head) else {
                bail!("commit not found: {new_head}");
            };

            let parents = &commit.parents;
            if parents.len() > 1 {
                bail!("commit history non-linear");
            }

            if new_head == start {
                break;
            }
            commits.push(new_head);

            new_head = parents.first().with_err_msg("no parent")?.to_string();
        }

        Ok(commits)
    }

    /// Fetch shallow tree for one commit, excluding blobs.
    ///
    /// # Errors
    ///
    /// Returns an error if the `fetch` command or parsing the response fails.
    pub fn fetch_shallow(&mut self, oid: &str) -> Result<(), Error> {
        let cmd = Fetch {
            want: vec![oid.to_owned()],
            have: vec![],
            filter: Some("blob:none".to_owned()),
            deepen: Some(1),
            hash_kind: self.hash_kind,
            include_tag: false,
        };

        let packfile = cmd.execute(self)?;

        for obj in packfile {
            let obj = obj.with_err_msg("could not get object")?;
            let hash = as_hex(obj.hash().as_ref());

            self.object_cache.insert(hash, obj);
        }

        Ok(())
    }
}