thin-fetch 0.1.0

Low-level Git crate to fetch objects with high granularity
Documentation
use std::str::FromStr;

use crate::{Command, Error, HashKind, Reference, WithError, bail, command::Execute};

/// List remote references.
///
/// ## Example
///
/// ```
/// # use thin_fetch::command::LsRefs;
/// # use thin_fetch::HashKind;
/// let ls_refs = LsRefs {
///     peel: false,
///     symrefs: false,
///     unborn: false,
///     ref_prefix: Some("refs/heads/".to_owned()),
///     hash_kind: HashKind::Sha1,
/// };
/// ```
#[derive(Default)]
pub struct LsRefs {
    pub peel: bool,
    pub symrefs: bool,
    pub unborn: bool,
    pub ref_prefix: Option<String>,
    pub hash_kind: HashKind,
}

impl From<&LsRefs> for Command {
    fn from(value: &LsRefs) -> Self {
        let mut cmd = Command::new("ls-refs").object_format(value.hash_kind);

        if value.peel {
            cmd = cmd.arg("peel");
        }
        if value.symrefs {
            cmd = cmd.arg("symrefs");
        }
        if value.unborn {
            cmd = cmd.arg("unborn");
        }
        if let Some(ref_prefix) = &value.ref_prefix {
            cmd = cmd.arg(format!("ref-prefix {ref_prefix}"));
        }

        cmd
    }
}

impl Execute for LsRefs {
    type Output = Vec<Reference>;

    fn execute(&self, repo: &crate::Repo) -> Result<Self::Output, Error> {
        let cmd = self.into();
        let parcel = repo.call(&cmd)?;

        if parcel.sections.is_empty() {
            return Ok(vec![]);
        }

        if parcel.sections.len() > 1 {
            bail!("expected 1 response, got {}", parcel.sections.len());
        }

        let refs = parcel.sections.into_iter().next().unwrap();

        refs.chunks
            .into_iter()
            .map(|a| {
                let str = String::from_utf8(a).with_err_msg("could not decode ref as utf-8");
                str.and_then(|s| Reference::from_str(&s).with_err_msg("could not parse reference"))
            })
            .collect()
    }
}