use crate::core::gh_identity::{GhIdentityError, resolve_gh_env, select_github_config};
use crate::core::trusty_tools_config::{GithubConfig, TrustyToolsConfig};
use crate::project::record::repo_url_matches;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GitIdentity {
pub env: Vec<(String, String)>,
pub commit_name: Option<String>,
pub commit_email: Option<String>,
}
impl GitIdentity {
pub fn is_empty(&self) -> bool {
self.env.is_empty() && self.commit_name.is_none() && self.commit_email.is_none()
}
pub fn commit_config_args(&self) -> Vec<String> {
let mut args = Vec::new();
if let Some(name) = &self.commit_name {
args.push("-c".to_string());
args.push(format!("user.name={name}"));
}
if let Some(email) = &self.commit_email {
args.push("-c".to_string());
args.push(format!("user.email={email}"));
}
args
}
}
pub fn resolve_git_identity(
project_github: Option<&GithubConfig>,
global_github: Option<&GithubConfig>,
commit_name: Option<&str>,
commit_email: Option<&str>,
) -> Result<GitIdentity, GhIdentityError> {
let selected = select_github_config(project_github, global_github);
let env = resolve_gh_env(selected)?;
Ok(GitIdentity {
env: env.vars().to_vec(),
commit_name: commit_name.map(str::to_string),
commit_email: commit_email.map(str::to_string),
})
}
pub fn resolve_for_config(
config: &TrustyToolsConfig,
repo_url: &str,
) -> Result<GitIdentity, GhIdentityError> {
let matched = config
.projects
.iter()
.find(|p| repo_url_matches(&p.repo_url, repo_url));
let project_github = matched.and_then(|p| p.github.as_ref());
let commit_name = matched.and_then(|p| p.commit_name.as_deref());
let commit_email = matched.and_then(|p| p.commit_email.as_deref());
resolve_git_identity(
project_github,
config.github.as_ref(),
commit_name,
commit_email,
)
}
#[cfg(test)]
mod tests {
use super::*;
fn gh(account: &str) -> GithubConfig {
GithubConfig {
account: None,
config_dir: Some(std::path::PathBuf::from(format!("/cfg/{account}"))),
token_env: None,
host: None,
}
}
#[test]
fn resolve_git_identity_project_overrides_global() {
let project = gh("project");
let global = gh("global");
let identity = resolve_git_identity(Some(&project), Some(&global), None, None).expect("ok");
assert_eq!(
identity.env,
vec![("GH_CONFIG_DIR".to_string(), "/cfg/project".to_string())]
);
}
#[test]
fn resolve_git_identity_falls_back_to_global() {
let global = gh("global");
let identity = resolve_git_identity(None, Some(&global), None, None).expect("ok");
assert_eq!(
identity.env,
vec![("GH_CONFIG_DIR".to_string(), "/cfg/global".to_string())]
);
}
#[test]
fn resolve_git_identity_none_when_nothing_configured() {
let identity = resolve_git_identity(None, None, None, None).expect("ok");
assert!(identity.is_empty());
}
#[test]
fn resolve_git_identity_commit_identity_independent_of_github() {
let identity =
resolve_git_identity(None, None, Some("Bot"), Some("bot@example.com")).expect("ok");
assert!(identity.env.is_empty());
assert_eq!(identity.commit_name.as_deref(), Some("Bot"));
assert_eq!(identity.commit_email.as_deref(), Some("bot@example.com"));
}
fn project_config(
repo_url: &str,
github: Option<GithubConfig>,
) -> crate::core::trusty_tools_config::ProjectConfig {
crate::core::trusty_tools_config::ProjectConfig {
name: "p".into(),
repo_url: repo_url.into(),
default_branch: None,
stack_hint: None,
tags: None,
description: None,
gh_user: None,
github,
commit_name: Some("Project Bot".into()),
commit_email: Some("bot@project.example.com".into()),
untracked_sync: None,
}
}
#[test]
fn resolve_for_config_matches_project_by_repo_url() {
let config = TrustyToolsConfig {
projects: vec![project_config(
"https://github.com/acme/widget.git",
Some(gh("project")),
)],
..Default::default()
};
let identity = resolve_for_config(&config, "https://github.com/acme/widget").expect("ok");
assert_eq!(
identity.env,
vec![("GH_CONFIG_DIR".to_string(), "/cfg/project".to_string())]
);
assert_eq!(identity.commit_name.as_deref(), Some("Project Bot"));
assert_eq!(
identity.commit_email.as_deref(),
Some("bot@project.example.com")
);
}
#[test]
fn resolve_for_config_falls_back_to_global_github() {
let config = TrustyToolsConfig {
github: Some(gh("global")),
projects: vec![project_config("https://github.com/acme/widget", None)],
..Default::default()
};
let identity = resolve_for_config(&config, "https://github.com/acme/widget").expect("ok");
assert_eq!(
identity.env,
vec![("GH_CONFIG_DIR".to_string(), "/cfg/global".to_string())]
);
}
#[test]
fn resolve_for_config_ambient_when_no_match() {
let config = TrustyToolsConfig::default();
let identity = resolve_for_config(&config, "https://github.com/someone/else").expect("ok");
assert!(identity.is_empty());
}
#[test]
fn commit_config_args_empty_when_unset() {
assert!(GitIdentity::default().commit_config_args().is_empty());
}
#[test]
fn commit_config_args_both_set() {
let identity = GitIdentity {
commit_name: Some("Bot".into()),
commit_email: Some("bot@example.com".into()),
..Default::default()
};
assert_eq!(
identity.commit_config_args(),
vec![
"-c".to_string(),
"user.name=Bot".to_string(),
"-c".to_string(),
"user.email=bot@example.com".to_string(),
]
);
}
#[test]
fn commit_config_args_name_only() {
let identity = GitIdentity {
commit_name: Some("Bot".into()),
..Default::default()
};
assert_eq!(
identity.commit_config_args(),
vec!["-c".to_string(), "user.name=Bot".to_string()]
);
}
}