use objects::object::Principal;
use repo::Repository;
use crate::ExecutionContext;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedPrincipal {
pub principal: Option<Principal>,
pub source: Option<&'static str>,
}
impl ResolvedPrincipal {
fn configured(principal: Principal, source: &'static str) -> Self {
if principal.name_lossy().trim().is_empty() || principal.email_lossy().trim().is_empty() {
return Self::unconfigured();
}
Self {
principal: Some(principal),
source: Some(source),
}
}
fn unconfigured() -> Self {
Self {
principal: None,
source: None,
}
}
}
pub fn resolve_principal(
repo: &Repository,
user_principal: Option<(&str, &str)>,
) -> repo::Result<ResolvedPrincipal> {
if let Some(resolved) = configured_from_env() {
return Ok(resolved);
}
if let Some(config) = &repo.config().principal {
return Ok(ResolvedPrincipal::configured(
Principal::new(&config.name, &config.email),
"repository",
));
}
if let Some(principal) = repo.configured_principal()? {
return Ok(ResolvedPrincipal::configured(principal, "git_config"));
}
Ok(finish_principal_resolution(user_principal))
}
pub fn resolve_principal_without_repo(user_principal: Option<(&str, &str)>) -> ResolvedPrincipal {
if let Some(resolved) = configured_from_env() {
return resolved;
}
finish_principal_resolution(user_principal)
}
fn configured_from_env() -> Option<ResolvedPrincipal> {
Principal::from_env().map(|principal| ResolvedPrincipal::configured(principal, "environment"))
}
fn finish_principal_resolution(user_principal: Option<(&str, &str)>) -> ResolvedPrincipal {
if let Some((name, email)) = user_principal {
return ResolvedPrincipal::configured(Principal::new(name, email), "user_config");
}
ResolvedPrincipal::unconfigured()
}
pub fn resolve_principal_from_context(
repo: &Repository,
ctx: &ExecutionContext,
) -> repo::Result<ResolvedPrincipal> {
let resolved = resolve_principal(repo, ctx.principal_fallback())?;
Ok(apply_hosted_principal_fallback(
resolved,
ctx.hosted_principal(),
))
}
pub fn apply_hosted_principal_fallback(
resolved: ResolvedPrincipal,
hosted: Option<(&str, &str)>,
) -> ResolvedPrincipal {
if resolved.principal.is_some() {
return resolved;
}
let Some((name, email)) = hosted else {
return resolved;
};
if name.trim().is_empty() {
return resolved;
}
ResolvedPrincipal::configured(Principal::new(name, email), "hosted_account")
}
pub fn principal_source_display(source: &str) -> &str {
match source {
"user_config" => "user_config (shared global config)",
"hosted_account" => "hosted_account",
_ => source,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn without_repo_user_pair_beats_unknown_fallback() {
let resolved = resolve_principal_without_repo(Some(("Luke", "luke@example.com")));
assert_eq!(resolved.source, Some("user_config"));
assert_eq!(
resolved
.principal
.as_ref()
.expect("configured")
.name_lossy(),
"Luke"
);
}
#[test]
fn without_repo_missing_pair_is_unconfigured() {
let resolved = resolve_principal_without_repo(None);
assert_eq!(resolved.source, None);
assert!(resolved.principal.is_none());
}
#[test]
fn display_labels_user_config_as_global() {
assert_eq!(
principal_source_display("user_config"),
"user_config (shared global config)"
);
assert_eq!(principal_source_display("environment"), "environment");
assert_eq!(principal_source_display("hosted_account"), "hosted_account");
}
#[test]
fn hosted_fallback_fills_unaccountable_local_principal() {
let unconfigured = resolve_principal_without_repo(None);
let derived =
apply_hosted_principal_fallback(unconfigured, Some(("luke", "luke@example.com")));
assert_eq!(derived.source, Some("hosted_account"));
assert_eq!(
derived.principal.as_ref().expect("hosted").name_lossy(),
"luke"
);
assert_eq!(
derived.principal.as_ref().expect("hosted").email_lossy(),
"luke@example.com"
);
}
#[test]
fn hosted_fallback_does_not_override_configured_principal() {
let local = resolve_principal_without_repo(Some(("Ada", "ada@example.com")));
let derived =
apply_hosted_principal_fallback(local.clone(), Some(("luke", "luke@example.com")));
assert_eq!(derived, local);
}
}