use std::time::SystemTime; use crate::{ bstr::BString, config, config::tree::{gitoxide, keys, Author, Committer, Key, User}, }; /// Identity handling. /// /// # Deviation /// /// There is no notion of a default user like in git, and instead failing to provide a user /// is fatal. That way, we enforce correctness and force application developers to take care /// of this issue which can be done in various ways, for instance by setting /// `gitoxide.committer.nameFallback` and similar. impl crate::Repository { /// Return the committer as configured by this repository, which is determined by… /// /// * …the git configuration `committer.name|email`… /// * …the `GIT_COMMITTER_(NAME|EMAIL|DATE)` environment variables… /// * …the configuration for `user.name|email` as fallback… /// /// …and in that order, or `None` if no committer name or email was configured, or `Some(Err(…))` /// if the committer date could not be parsed. /// /// # Note /// /// The values are cached when the repository is instantiated. pub fn committer(&self) -> Option, config::time::Error>> { let p = self.config.personas(); Ok(gix_actor::SignatureRef { name: p.committer.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?, email: p .committer .email .as_ref() .or(p.user.email.as_ref()) .map(|v| v.as_ref())?, time: match extract_time_or_default(p.committer.time.as_ref(), &gitoxide::Commit::COMMITTER_DATE) { Ok(t) => t, Err(err) => return Some(Err(err)), }, }) .into() } /// Return the author as configured by this repository, which is determined by… /// /// * …the git configuration `author.name|email`… /// * …the `GIT_AUTHOR_(NAME|EMAIL|DATE)` environment variables… /// * …the configuration for `user.name|email` as fallback… /// /// …and in that order, or `None` if there was nothing configured. /// /// # Note /// /// The values are cached when the repository is instantiated. pub fn author(&self) -> Option, config::time::Error>> { let p = self.config.personas(); Ok(gix_actor::SignatureRef { name: p.author.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?, email: p.author.email.as_ref().or(p.user.email.as_ref()).map(|v| v.as_ref())?, time: match extract_time_or_default(p.author.time.as_ref(), &gitoxide::Commit::AUTHOR_DATE) { Ok(t) => t, Err(err) => return Some(Err(err)), }, }) .into() } } fn extract_time_or_default( time: Option<&Result>, config_key: &'static keys::Time, ) -> Result { match time { Some(Ok(t)) => Ok(*t), None => Ok(gix_date::Time::now_local_or_utc()), Some(Err(err)) => Err(config::time::Error::from(config_key).with_source(err.clone())), } } #[derive(Debug, Clone)] pub(crate) struct Entity { pub name: Option, pub email: Option, /// A time parsed from an environment variable, handling potential errors is delayed. pub time: Option>, } #[derive(Debug, Clone)] pub(crate) struct Personas { user: Entity, committer: Entity, author: Entity, } impl Personas { pub fn from_config_and_env(config: &gix_config::File<'_>) -> Self { fn entity_in_section( config: &gix_config::File<'_>, name_key: &keys::Any, email_key: &keys::Any, fallback: Option<(&keys::Any, &keys::Any)>, ) -> (Option, Option) { let fallback = fallback.and_then(|(name_key, email_key)| { debug_assert_eq!(name_key.section.name(), email_key.section.name()); config .section("gitoxide", Some(name_key.section.name().into())) .ok() .map(|section| (section, name_key, email_key)) }); ( config .string(name_key.section.name(), None, name_key.name) .or_else(|| fallback.as_ref().and_then(|(s, name_key, _)| s.value(name_key.name))) .map(|v| v.into_owned()), config .string(email_key.section.name(), None, email_key.name) .or_else(|| fallback.as_ref().and_then(|(s, _, email_key)| s.value(email_key.name))) .map(|v| v.into_owned()), ) } let now = SystemTime::now(); let parse_date = |key: &str, date: &keys::Time| -> Option> { debug_assert_eq!( key, date.logical_name(), "BUG: drift of expected name and actual name of the key (we hardcode it to save an allocation)" ); config .string_by_key(key) .map(|time| date.try_into_time(time, now.into())) }; let fallback = ( &gitoxide::Committer::NAME_FALLBACK, &gitoxide::Committer::EMAIL_FALLBACK, ); let (committer_name, committer_email) = entity_in_section(config, &Committer::NAME, &Committer::EMAIL, Some(fallback)); let fallback = (&gitoxide::Author::NAME_FALLBACK, &gitoxide::Author::EMAIL_FALLBACK); let (author_name, author_email) = entity_in_section(config, &Author::NAME, &Author::EMAIL, Some(fallback)); let (user_name, mut user_email) = entity_in_section(config, &User::NAME, &User::EMAIL, None); let committer_date = parse_date("gitoxide.commit.committerDate", &gitoxide::Commit::COMMITTER_DATE); let author_date = parse_date("gitoxide.commit.authorDate", &gitoxide::Commit::AUTHOR_DATE); user_email = user_email.or_else(|| { config .string_by_key(gitoxide::User::EMAIL_FALLBACK.logical_name().as_str()) .map(|v| v.into_owned()) }); Personas { user: Entity { name: user_name, email: user_email, time: None, }, committer: Entity { name: committer_name, email: committer_email, time: committer_date, }, author: Entity { name: author_name, email: author_email, time: author_date, }, } } }