use std::ops::Deref as _; use git_next_config::{Hostname, RepoPath}; use gix::remote::Direction; use tracing::info; use super::{GitRemote, RepoDetails, Repository}; #[tracing::instrument(skip_all)] pub fn validate(repository: &Repository, repo_details: &RepoDetails) -> Result<()> { let git_remote = repo_details.git_remote(); let push_remote = find_default_remote(repository, Direction::Push)?; let fetch_remote = find_default_remote(repository, Direction::Fetch)?; info!(config = %git_remote, push = %push_remote, fetch = %fetch_remote, "Check remotes match"); if git_remote != push_remote { return Err(Error::MismatchDefaultPushRemote { found: push_remote, expected: git_remote, }); } if git_remote != fetch_remote { return Err(Error::MismatchDefaultFetchRemote { found: fetch_remote, expected: git_remote, }); } Ok(()) } #[tracing::instrument(skip_all, fields(?direction))] pub fn find_default_remote( repository: &Repository, direction: gix::remote::Direction, ) -> Result { let repository = repository.deref().to_thread_local(); let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else { return Err(Error::NoDefaultPushRemote); }; let Some(url) = remote.url(direction) else { return Err(Error::NoUrlForDefaultPushRemote); }; let Some(host) = url.host() else { return Err(Error::NoHostnameForDefaultPushRemote); }; let path = url.path.to_string(); let path = path.strip_prefix('/').map_or(path.as_str(), |path| path); let path = path.strip_suffix(".git").map_or(path, |path| path); info!(%host, %path, "found"); Ok(GitRemote::new( Hostname::new(host), RepoPath(path.to_string()), )) } type Result = core::result::Result; #[derive(Debug)] pub enum Error { NoDefaultPushRemote, NoUrlForDefaultPushRemote, NoHostnameForDefaultPushRemote, UnableToOpenRepo(String), Io(std::io::Error), MismatchDefaultPushRemote { found: GitRemote, expected: GitRemote, }, MismatchDefaultFetchRemote { found: GitRemote, expected: GitRemote, }, } impl std::error::Error for Error {} impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::NoDefaultPushRemote => write!(f, "There is no default push remote"), Self::NoUrlForDefaultPushRemote => write!(f, "The default push remote has no url"), Self::NoHostnameForDefaultPushRemote => { write!(f, "The default push remote has no hostname") } Self::UnableToOpenRepo(err) => write!(f, "Unable to open the git dir: {err}"), Self::Io(err) => write!(f, "IO Error: {err:?}"), Self::MismatchDefaultPushRemote { found, expected } => write!( f, "The default push remote doesn't match: {found}, expected: {expected}" ), Self::MismatchDefaultFetchRemote { found, expected } => write!( f, "The default fetch remote doesn't match: {found}, expected: {expected}" ), } } }