1use std::path::Path;
7
8use crate::provider::ProviderKind;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Detected {
14 pub kind: ProviderKind,
15 pub host: Option<String>,
16}
17
18pub fn detect(
21 repo_root: &Path,
22 remote_host: Option<&str>,
23 forced: Option<ProviderKind>,
24) -> Option<Detected> {
25 let has_gh = repo_root.join(".github/workflows").is_dir();
26 let has_gitlab = repo_root.join(".gitlab-ci.yml").is_file();
27 classify(remote_host, has_gh, has_gitlab, forced)
28}
29
30fn classify(
33 remote_host: Option<&str>,
34 has_gh_workflows: bool,
35 has_gitlab_ci: bool,
36 forced: Option<ProviderKind>,
37) -> Option<Detected> {
38 let self_hosted = |host: Option<&str>| {
39 host.filter(|h| !h.eq_ignore_ascii_case("gitlab.com"))
40 .map(str::to_owned)
41 };
42
43 if let Some(kind) = forced {
45 return Some(Detected { kind, host: None });
46 }
47
48 if let Some(host) = remote_host {
49 if host.eq_ignore_ascii_case("github.com") {
50 return Some(Detected {
51 kind: ProviderKind::GitHub,
52 host: None,
53 });
54 }
55 if host.eq_ignore_ascii_case("gitlab.com") || host.to_ascii_lowercase().contains("gitlab") {
56 return Some(Detected {
57 kind: ProviderKind::GitLab,
58 host: self_hosted(Some(host)),
59 });
60 }
61 }
62
63 if has_gh_workflows {
65 return Some(Detected {
66 kind: ProviderKind::GitHub,
67 host: None,
68 });
69 }
70 if has_gitlab_ci {
71 return Some(Detected {
72 kind: ProviderKind::GitLab,
73 host: self_hosted(remote_host),
74 });
75 }
76 None
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn github_dot_com_is_github() {
85 assert_eq!(
86 classify(Some("github.com"), false, false, None),
87 Some(Detected {
88 kind: ProviderKind::GitHub,
89 host: None
90 })
91 );
92 }
93
94 #[test]
95 fn gitlab_dot_com_carries_no_host() {
96 assert_eq!(
97 classify(Some("gitlab.com"), false, false, None),
98 Some(Detected {
99 kind: ProviderKind::GitLab,
100 host: None
101 })
102 );
103 }
104
105 #[test]
106 fn self_hosted_gitlab_host_with_config_file() {
107 assert_eq!(
108 classify(Some("git.example.com"), false, true, None),
109 Some(Detected {
110 kind: ProviderKind::GitLab,
111 host: Some("git.example.com".into())
112 })
113 );
114 }
115
116 #[test]
117 fn unknown_host_falls_back_to_workflows_dir() {
118 assert_eq!(
119 classify(Some("git.example.com"), true, false, None),
120 Some(Detected {
121 kind: ProviderKind::GitHub,
122 host: None
123 })
124 );
125 }
126
127 #[test]
128 fn no_signals_is_none() {
129 assert_eq!(classify(None, false, false, None), None);
130 }
131
132 #[test]
133 fn forced_overrides_detection() {
134 assert_eq!(
135 classify(Some("github.com"), true, false, Some(ProviderKind::GitLab)),
136 Some(Detected {
137 kind: ProviderKind::GitLab,
138 host: None
139 })
140 );
141 }
142}