use crate::config::Scheme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchemeFlag {
Ssh,
Https,
Unset,
}
#[derive(Debug, Clone)]
pub struct ResolvedScheme {
pub scheme: Scheme,
pub token: Option<String>,
#[allow(dead_code)]
pub source: &'static str,
}
pub trait Env {
fn get(&self, key: &str) -> Option<String>;
}
pub struct SystemEnv;
impl Env for SystemEnv {
fn get(&self, key: &str) -> Option<String> {
std::env::var(key).ok()
}
}
pub fn resolve(flag: SchemeFlag, config_default: Scheme, env: &dyn Env) -> ResolvedScheme {
match flag {
SchemeFlag::Ssh => {
return ResolvedScheme {
scheme: Scheme::Ssh,
token: None,
source: "flag",
}
}
SchemeFlag::Https => {
return ResolvedScheme {
scheme: Scheme::Https,
token: ci_token(env),
source: "flag",
}
}
SchemeFlag::Unset => {}
}
if let Some(v) = env.get("RV_REMOTE_SCHEME") {
match v.to_ascii_lowercase().as_str() {
"ssh" => {
return ResolvedScheme {
scheme: Scheme::Ssh,
token: None,
source: "env",
}
}
"https" => {
return ResolvedScheme {
scheme: Scheme::Https,
token: ci_token(env),
source: "env",
}
}
_ => {}
}
}
if is_ci(env) {
return ResolvedScheme {
scheme: Scheme::Https,
token: ci_token(env),
source: "ci",
};
}
ResolvedScheme {
scheme: config_default,
token: None,
source: "config",
}
}
fn is_ci(env: &dyn Env) -> bool {
env.get("GITHUB_ACTIONS").is_some()
|| env
.get("CI")
.map(|v| v != "false" && !v.is_empty())
.unwrap_or(false)
}
fn ci_token(env: &dyn Env) -> Option<String> {
env.get("RV_GIT_TOKEN")
.or_else(|| env.get("GH_TOKEN"))
.or_else(|| env.get("GITHUB_TOKEN"))
}
pub fn url_for(host: &str, name: &str, resolved: &ResolvedScheme) -> String {
let name = name.trim_end_matches(".git");
match resolved.scheme {
Scheme::Ssh => format!("git@{host}:{name}.git"),
Scheme::Https => match &resolved.token {
Some(t) => format!("https://x-access-token:{t}@{host}/{name}.git"),
None => format!("https://{host}/{name}.git"),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct Map(HashMap<String, String>);
impl Env for Map {
fn get(&self, k: &str) -> Option<String> {
self.0.get(k).cloned()
}
}
fn env(pairs: &[(&str, &str)]) -> Map {
Map(pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect())
}
#[test]
fn flag_wins() {
let r = resolve(SchemeFlag::Https, Scheme::Ssh, &env(&[]));
assert_eq!(r.scheme, Scheme::Https);
assert_eq!(r.source, "flag");
}
#[test]
fn ci_forces_https_with_token() {
let r = resolve(
SchemeFlag::Unset,
Scheme::Ssh,
&env(&[("GITHUB_ACTIONS", "true"), ("GITHUB_TOKEN", "tok")]),
);
assert_eq!(r.scheme, Scheme::Https);
assert_eq!(r.source, "ci");
assert_eq!(r.token.as_deref(), Some("tok"));
}
#[test]
fn falls_back_to_config() {
let r = resolve(SchemeFlag::Unset, Scheme::Ssh, &env(&[]));
assert_eq!(r.scheme, Scheme::Ssh);
assert_eq!(r.source, "config");
}
#[test]
fn url_forms() {
let ssh = ResolvedScheme {
scheme: Scheme::Ssh,
token: None,
source: "x",
};
assert_eq!(
url_for("github.com", "acme/foo", &ssh),
"git@github.com:acme/foo.git"
);
let https = ResolvedScheme {
scheme: Scheme::Https,
token: None,
source: "x",
};
assert_eq!(
url_for("github.com", "acme/foo", &https),
"https://github.com/acme/foo.git"
);
let tok = ResolvedScheme {
scheme: Scheme::Https,
token: Some("T".into()),
source: "x",
};
assert_eq!(
url_for("github.com", "acme/foo", &tok),
"https://x-access-token:T@github.com/acme/foo.git"
);
}
#[test]
fn env_scheme_override() {
let r = resolve(
SchemeFlag::Unset,
Scheme::Ssh,
&env(&[("RV_REMOTE_SCHEME", "https")]),
);
assert_eq!(r.scheme, Scheme::Https);
assert_eq!(r.source, "env");
}
}