use std::sync::OnceLock;
use worktrunk::git::{GitRemoteUrl, Repository};
use super::{CiBranchName, PrStatus, github, gitlab, tool_available};
static CI_TOOLS: OnceLock<CiToolsAvailable> = OnceLock::new();
struct CiToolsAvailable {
gh: bool,
glab: bool,
}
impl CiToolsAvailable {
fn get() -> &'static Self {
CI_TOOLS.get_or_init(|| Self {
gh: tool_available("gh", &["--version"]),
glab: tool_available("glab", &["--version"]),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumString)]
#[strum(serialize_all = "lowercase")]
pub enum CiPlatform {
GitHub,
GitLab,
}
impl CiPlatform {
fn is_tool_available(self) -> bool {
match self {
Self::GitHub => CiToolsAvailable::get().gh,
Self::GitLab => CiToolsAvailable::get().glab,
}
}
fn detect_pr_mr(
self,
repo: &Repository,
branch: &CiBranchName,
local_head: &str,
) -> Option<PrStatus> {
match self {
Self::GitHub => github::detect_github(repo, branch, local_head),
Self::GitLab => gitlab::detect_gitlab(repo, branch, local_head),
}
}
fn detect_branch(
self,
repo: &Repository,
branch: &CiBranchName,
local_head: &str,
) -> Option<PrStatus> {
match self {
Self::GitHub => github::detect_github_commit_checks(repo, branch, local_head),
Self::GitLab => gitlab::detect_gitlab_pipeline(&branch.name, local_head),
}
}
pub(super) fn detect_ci(
self,
repo: &Repository,
branch: &CiBranchName,
local_head: &str,
has_upstream: bool,
) -> Option<PrStatus> {
if !self.is_tool_available() {
return None;
}
if let Some(status) = self.detect_pr_mr(repo, branch, local_head) {
return Some(status);
}
if has_upstream {
return self.detect_branch(repo, branch, local_head);
}
None
}
}
pub fn detect_platform_from_url(url: &str) -> Option<CiPlatform> {
let parsed = GitRemoteUrl::parse(url)?;
if parsed.is_github() {
Some(CiPlatform::GitHub)
} else if parsed.is_gitlab() {
Some(CiPlatform::GitLab)
} else {
None
}
}
pub fn platform_for_repo(repo: &Repository, remote_hint: Option<&str>) -> Option<CiPlatform> {
if let Some(platform_str) = repo
.load_project_config()
.ok()
.flatten()
.and_then(|c| c.forge_platform().map(str::to_string))
{
if let Ok(platform) = platform_str.parse::<CiPlatform>() {
log::debug!("Using CI platform from config override: {}", platform);
return Some(platform);
}
log::warn!(
"Invalid CI platform in config: '{}'. Expected 'github' or 'gitlab'.",
platform_str
);
}
if let Some(remote_name) = remote_hint
&& let Some(url) = repo.effective_remote_url(remote_name)
&& let Some(platform) = detect_platform_from_url(&url)
{
log::debug!(
"Detected CI platform {} from remote '{}' (hint)",
platform,
remote_name
);
return Some(platform);
}
if let Some(remote) = repo.primary_remote().ok()
&& let Some(url) = repo.effective_remote_url(&remote)
&& let Some(platform) = detect_platform_from_url(&url)
{
log::debug!("Detected CI platform {} from remote '{}'", platform, remote);
return Some(platform);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_platform_from_url() {
assert_eq!(
detect_platform_from_url("https://github.com/owner/repo.git"),
Some(CiPlatform::GitHub)
);
assert_eq!(
detect_platform_from_url("git@github.com:owner/repo.git"),
Some(CiPlatform::GitHub)
);
assert_eq!(
detect_platform_from_url("ssh://git@github.com/owner/repo.git"),
Some(CiPlatform::GitHub)
);
assert_eq!(
detect_platform_from_url("https://github.mycompany.com/owner/repo.git"),
Some(CiPlatform::GitHub)
);
assert_eq!(
detect_platform_from_url("https://gitlab.com/owner/repo.git"),
Some(CiPlatform::GitLab)
);
assert_eq!(
detect_platform_from_url("git@gitlab.com:owner/repo.git"),
Some(CiPlatform::GitLab)
);
assert_eq!(
detect_platform_from_url("https://gitlab.example.com/owner/repo.git"),
Some(CiPlatform::GitLab)
);
assert_eq!(
detect_platform_from_url("http://github.com/owner/repo.git"),
Some(CiPlatform::GitHub)
);
assert_eq!(
detect_platform_from_url("git://github.com/owner/repo.git"),
Some(CiPlatform::GitHub)
);
assert_eq!(
detect_platform_from_url("http://gitlab.example.com/owner/repo.git"),
Some(CiPlatform::GitLab)
);
assert_eq!(
detect_platform_from_url("git://gitlab.mycompany.com/owner/repo.git"),
Some(CiPlatform::GitLab)
);
assert_eq!(
detect_platform_from_url("https://bitbucket.org/owner/repo.git"),
None
);
assert_eq!(
detect_platform_from_url("https://codeberg.org/owner/repo.git"),
None
);
}
#[test]
fn test_platform_override_github() {
assert_eq!(
"github".parse::<CiPlatform>().ok(),
Some(CiPlatform::GitHub)
);
}
#[test]
fn test_platform_override_gitlab() {
assert_eq!(
"gitlab".parse::<CiPlatform>().ok(),
Some(CiPlatform::GitLab)
);
}
#[test]
fn test_platform_override_invalid() {
assert!("invalid".parse::<CiPlatform>().is_err());
assert!("GITHUB".parse::<CiPlatform>().is_err()); assert!("GitHub".parse::<CiPlatform>().is_err()); }
}