veryl-metadata 0.19.0

A modern hardware description language
Documentation
use crate::metadata_error::MetadataError;
use git_repository::Repository;
use git_repository::index::File;
use git_repository::objs::BlobRef;
use git_repository::prelude::FindExt;
use git_repository::progress::Discard;
use git_repository::remote::Direction;
use git_repository::remote::ref_map::Options;
use git_repository::worktree::index;
use log::debug;
use std::path::Path;
use std::sync::atomic::AtomicBool;
use thiserror::Error;
use url::Url;

pub struct Git {
    rev: Option<String>,
    tag: Option<String>,
    branch: Option<String>,
    repo: Repository,
}

#[derive(Error, Debug)]
pub enum GitoxideError {
    #[error("git error")]
    Open(#[from] git_repository::open::Error),

    #[error("git error")]
    UrlParse(#[from] git_repository::url::parse::Error),

    #[error("git error")]
    Clone(#[from] git_repository::clone::Error),

    #[error("git error")]
    CloneFetch(#[from] git_repository::clone::fetch::Error),

    #[error("git error")]
    CloneCheckoutMainWorktree(#[from] git_repository::clone::checkout::main_worktree::Error),

    #[error("git error")]
    RemoteFindExisting(#[from] git_repository::remote::find::existing::Error),

    #[error("git error")]
    RemoteConnect(#[from] git_repository::remote::connect::Error),

    #[error("git error")]
    RemoteFetchPrepare(#[from] git_repository::remote::fetch::prepare::Error),

    #[error("git error")]
    RemoteFetch(#[from] git_repository::remote::fetch::Error),

    #[error("git error")]
    ReferenceFindExisting(#[from] git_repository::reference::find::existing::Error),

    #[error("git error")]
    ReferencePeel(#[from] git_repository::reference::peel::Error),

    #[error("git error")]
    IndexFileInit(#[from] git_repository::index::file::init::Error),

    #[error("git error")]
    WorktreeIndexCheckout(
        #[from] git_repository::worktree::index::checkout::Error<Box<GitoxideError>>,
    ),

    #[error("git error")]
    RevisionSpecParseSingle(#[from] git_repository::revision::spec::parse::single::Error),

    #[error("git error")]
    OdbStoreFind(
        #[from]
        git_repository::odb::find::existing_object::Error<git_repository::odb::store::find::Error>,
    ),
}

impl From<GitoxideError> for MetadataError {
    fn from(x: GitoxideError) -> MetadataError {
        MetadataError::Git(Box::new(x))
    }
}

macro_rules! from_for_metadata_error {
    ($x:ty) => {
        impl From<$x> for MetadataError {
            fn from(x: $x) -> MetadataError {
                x.into()
            }
        }
    };
}

from_for_metadata_error!(git_repository::open::Error);
from_for_metadata_error!(git_repository::url::parse::Error);
from_for_metadata_error!(git_repository::clone::Error);
from_for_metadata_error!(git_repository::clone::fetch::Error);
from_for_metadata_error!(git_repository::clone::checkout::main_worktree::Error);
from_for_metadata_error!(git_repository::remote::find::existing::Error);
from_for_metadata_error!(git_repository::remote::connect::Error);
from_for_metadata_error!(git_repository::remote::fetch::prepare::Error);
from_for_metadata_error!(git_repository::remote::fetch::Error);
from_for_metadata_error!(git_repository::reference::find::existing::Error);
from_for_metadata_error!(git_repository::reference::peel::Error);
from_for_metadata_error!(git_repository::index::file::init::Error);
from_for_metadata_error!(git_repository::worktree::index::checkout::Error<Box<GitoxideError>>);
from_for_metadata_error!(
    git_repository::worktree::index::checkout::Error<
        git_repository::odb::find::existing_object::Error<git_repository::odb::store::find::Error>,
    >
);
from_for_metadata_error!(git_repository::revision::spec::parse::single::Error);
from_for_metadata_error!(
    git_repository::odb::find::existing_object::Error<git_repository::odb::store::find::Error>
);

impl Git {
    pub fn clone(
        url: &Url,
        path: &Path,
        rev: Option<&str>,
        tag: Option<&str>,
        branch: Option<&str>,
    ) -> Result<Self, MetadataError> {
        std::env::set_var("GIT_COMMITTER_NAME", "veryl");
        std::env::set_var("GIT_COMMITTER_EMAIL", "veryl");

        let repo = if path.exists() {
            git_repository::open(path)?
        } else {
            let git_url: git_repository::Url = url.as_str().try_into()?;
            let mut repo = git_repository::prepare_clone(git_url, path)?;
            let (mut repo, _) = repo.fetch_then_checkout(Discard, &AtomicBool::new(false))?;
            let (repo, _) = repo.main_worktree(Discard, &AtomicBool::new(false))?;
            debug!("Cloned repository ({})", url);
            repo
        };

        Ok(Git {
            rev: rev.map(|x| x.to_owned()),
            tag: tag.map(|x| x.to_owned()),
            branch: branch.map(|x| x.to_owned()),
            repo,
        })
    }

    pub fn fetch(&self) -> Result<(), MetadataError> {
        let remote = self.repo.find_default_remote(Direction::Fetch).unwrap()?;
        let connect = remote.connect(Direction::Fetch, Discard)?;
        let prepare = connect.prepare_fetch(Options::default())?;
        let _outcome = prepare.receive(&AtomicBool::new(false))?;

        debug!(
            "Fetched repository ({})",
            self.repo.work_dir().unwrap().to_string_lossy()
        );

        Ok(())
    }

    pub fn checkout(&self) -> Result<(), MetadataError> {
        let id = if let Some(ref rev) = self.rev {
            self.repo.rev_parse_single(rev.as_str())?
        } else {
            let dst = if let Some(ref tag) = self.tag {
                tag.to_string()
            } else if let Some(ref branch) = self.branch {
                format!("origin/{branch}")
            } else {
                "origin/HEAD".to_string()
            };

            let mut reference = self.repo.find_reference(&dst)?;
            reference.peel_to_id_in_place()?
        };

        let file = File::at(self.repo.index_path(), id.kind(), Default::default())?;
        let (mut state, _path) = file.into_parts();
        index::checkout(
            &mut state,
            self.repo.work_dir().unwrap(),
            {
                let objects = self.repo.objects.clone().into_arc()?;
                move |oid, buf| objects.find_blob(oid, buf)
            },
            &mut Discard,
            &mut Discard,
            &AtomicBool::new(false),
            Default::default(),
        )?;

        debug!(
            "Checkouted repository ({})",
            self.repo.work_dir().unwrap().to_string_lossy()
        );
        Ok(())
    }
}