git-next-core 0.14.1

core for git-next, the trunk-based development manager
Documentation
//
use crate::{
    git::{
        self,
        repository::open::{OpenRepositoryLike, RealOpenRepository},
    },
    s, BranchName, ForgeDetails, GitDir, RemoteUrl, RepoBranches,
};

use derive_more::Constructor;

use std::{
    path::Path,
    sync::{Arc, RwLock},
};

pub type OnFetchFn = fn(&RepoBranches, &GitDir, &kxio::fs::FileSystem) -> git::fetch::Result<()>;
#[derive(Clone, Debug, Constructor)]
pub struct OnFetch {
    repo_branches: RepoBranches,
    gitdir: GitDir,
    fs: kxio::fs::FileSystem,
    action: OnFetchFn,
}
impl OnFetch {
    /// Invokes the action function.
    ///
    /// # Errors
    ///
    /// Will return any `Err` if there is no fetch remote defined in .git/config
    /// of if there are any network connectivity issues with the remote server.
    pub fn invoke(&self) -> git::fetch::Result<()> {
        (self.action)(&self.repo_branches, &self.gitdir, &self.fs)
    }
}

pub type OnPushFn = fn(
    &git::RepoDetails,
    &BranchName,
    &git::GitRef,
    &git::push::Force,
    &RepoBranches,
    &GitDir,
    &kxio::fs::FileSystem,
) -> git::push::Result<()>;
#[derive(Clone, Debug, Constructor)]
pub struct OnPush {
    repo_branches: RepoBranches,
    gitdir: GitDir,
    fs: kxio::fs::FileSystem,
    action: OnPushFn,
}
impl OnPush {
    /// Invokes the action function.
    ///
    /// # Errors
    ///
    /// Will return any `Err` if there is no push remote defined in .git/config
    /// of if there are any network connectivity issues with the remote server.
    pub fn invoke(
        &self,
        repo_details: &git::RepoDetails,
        branch_name: &BranchName,
        to_commit: &git::GitRef,
        force: &git::push::Force,
    ) -> git::push::Result<()> {
        (self.action)(
            repo_details,
            branch_name,
            to_commit,
            force,
            &self.repo_branches,
            &self.gitdir,
            &self.fs,
        )
    }
}

#[derive(Clone, Debug)]
pub struct TestOpenRepository {
    on_fetch: Vec<OnFetch>,
    fetch_counter: Arc<RwLock<usize>>,
    on_push: Vec<OnPush>,
    push_counter: Arc<RwLock<usize>>,
    real: RealOpenRepository,
}
#[cfg(not(tarpaulin_include))]
impl git::repository::OpenRepositoryLike for TestOpenRepository {
    fn remote_branches(&self) -> git::push::Result<Vec<BranchName>> {
        self.real.remote_branches()
    }

    fn find_default_remote(&self, direction: git::repository::Direction) -> Option<RemoteUrl> {
        self.real.find_default_remote(direction)
    }

    fn fetch(&self) -> Result<(), git::fetch::Error> {
        let i: usize = *self
            .fetch_counter
            .read()
            .map_err(|_| git::fetch::Error::Lock)?;
        self.fetch_counter
            .write()
            .map_err(|_| git::fetch::Error::Lock)
            .map(|mut c| *c += 1)?;
        #[allow(clippy::expect_used)]
        self.on_fetch
            .get(i)
            .map(OnFetch::invoke)
            .expect("unexpected fetch")
    }

    fn push(
        &self,
        repo_details: &git::RepoDetails,
        branch_name: &BranchName,
        to_commit: &git::GitRef,
        force: &git::push::Force,
    ) -> git::push::Result<()> {
        let i: usize = *self
            .push_counter
            .read()
            .map_err(|_| git::fetch::Error::Lock)?;
        println!("Push: {i}");
        self.push_counter
            .write()
            .map_err(|_| git::fetch::Error::Lock)
            .map(|mut c| *c += 1)?;
        #[allow(clippy::expect_used)]
        self.on_push
            .get(i)
            .map(|f| f.invoke(repo_details, branch_name, to_commit, force))
            .expect("unexpected push")
    }

    fn commit_log(
        &self,
        branch_name: &BranchName,
        find_commits: &[git::Commit],
    ) -> git::commit::log::Result<Vec<git::Commit>> {
        self.real.commit_log(branch_name, find_commits)
    }

    fn read_file(&self, branch_name: &BranchName, file_name: &Path) -> git::file::Result<String> {
        self.real.read_file(branch_name, file_name)
    }

    fn duplicate(&self) -> Box<dyn OpenRepositoryLike> {
        Box::new(self.clone())
    }
}
impl TestOpenRepository {
    pub(crate) fn new(
        gitdir: &GitDir,
        fs: &kxio::fs::FileSystem,
        on_fetch: Vec<OnFetch>,
        on_push: Vec<OnPush>,
        forge_details: ForgeDetails,
    ) -> Self {
        let pathbuf = fs.base().join(gitdir.to_path_buf());
        #[allow(clippy::expect_used)]
        let gix = gix::init(pathbuf).expect("git init");
        Self::write_origin(gitdir, fs);
        Self {
            on_fetch,
            fetch_counter: Arc::new(RwLock::new(0)),
            on_push,
            push_counter: Arc::new(RwLock::new(0)),
            real: RealOpenRepository::new(Arc::new(RwLock::new(gix.into())), forge_details),
        }
    }

    fn write_origin(gitdir: &GitDir, fs: &kxio::fs::FileSystem) {
        let config_file = fs.base().join(gitdir.to_path_buf()).join(".git/config");
        let file = fs.file(&config_file);
        #[allow(clippy::expect_used)]
        let contents = s!(file.reader().expect("read original .git/config"));
        let updated_contents = format!(
            r#"{contents}
[remote "origin"]
    url = git@foo.example,net
    fetch = +refs/heads/*:refs/remotes/origin/*
        "#
        );
        #[allow(clippy::expect_used)]
        file.write(&updated_contents)
            .expect("write updated .git/config");
    }
}