1use objects::object::Principal;
9use repo::Repository;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ResolvedPrincipal {
14 pub principal: Principal,
15 pub source: Option<&'static str>,
16}
17
18impl ResolvedPrincipal {
19 fn configured(principal: Principal, source: &'static str) -> Self {
20 Self {
21 principal,
22 source: Some(source),
23 }
24 }
25
26 fn unknown(principal: Principal) -> Self {
27 Self {
28 principal,
29 source: None,
30 }
31 }
32}
33
34pub fn resolve_principal(
42 repo: &Repository,
43 user_principal: Option<(&str, &str)>,
44) -> repo::Result<ResolvedPrincipal> {
45 if let Some(resolved) = configured_from_env() {
46 return Ok(resolved);
47 }
48 if let Some(config) = &repo.config().principal {
49 return Ok(ResolvedPrincipal::configured(
50 Principal::new(&config.name, &config.email),
51 "repository",
52 ));
53 }
54 let principal = repo.get_principal()?;
55 if principal_is_accountable(&principal) {
56 return Ok(ResolvedPrincipal::configured(principal, "git_config"));
57 }
58 Ok(finish_principal_resolution(user_principal, principal))
59}
60
61pub fn resolve_principal_without_repo(user_principal: Option<(&str, &str)>) -> ResolvedPrincipal {
66 if let Some(resolved) = configured_from_env() {
67 return resolved;
68 }
69 finish_principal_resolution(
70 user_principal,
71 Principal::new("Unknown", "unknown@example.com"),
72 )
73}
74
75fn configured_from_env() -> Option<ResolvedPrincipal> {
76 Principal::from_env().map(|principal| ResolvedPrincipal::configured(principal, "environment"))
77}
78
79fn finish_principal_resolution(
80 user_principal: Option<(&str, &str)>,
81 fallback: Principal,
82) -> ResolvedPrincipal {
83 if let Some((name, email)) = user_principal {
84 return ResolvedPrincipal::configured(Principal::new(name, email), "user_config");
85 }
86 ResolvedPrincipal::unknown(fallback)
87}
88
89pub fn principal_source_display(source: &str) -> &str {
93 match source {
94 "user_config" => "user_config (shared global config)",
95 _ => source,
96 }
97}
98
99fn principal_is_accountable(principal: &Principal) -> bool {
100 let name = principal.name_lossy();
101 let email = principal.email_lossy();
102 let name = name.trim();
103 let email = email.trim();
104 !name.is_empty() && !email.is_empty() && !(name == "Unknown" && email == "unknown@example.com")
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn without_repo_user_pair_beats_unknown_fallback() {
113 let resolved = resolve_principal_without_repo(Some(("Luke", "luke@example.com")));
114 assert_eq!(resolved.source, Some("user_config"));
115 assert_eq!(resolved.principal.name_lossy(), "Luke");
116 }
117
118 #[test]
119 fn without_repo_missing_pair_falls_back_to_unknown() {
120 let resolved = resolve_principal_without_repo(None);
121 assert_eq!(resolved.source, None);
122 assert_eq!(resolved.principal.email_lossy(), "unknown@example.com");
123 }
124
125 #[test]
126 fn display_labels_user_config_as_global() {
127 assert_eq!(
128 principal_source_display("user_config"),
129 "user_config (shared global config)"
130 );
131 assert_eq!(principal_source_display("environment"), "environment");
132 }
133}