use crate::Contributor;
use std::collections::BTreeMap;
type Registry = BTreeMap<String, Contributor>;
pub fn curated(contributor: &Contributor) -> bool {
!contributor.emails.contains(&contributor.alias)
}
pub fn fold(target: &mut Contributor, source: &Contributor) {
for name in &source.names {
target.names.insert(name.clone());
}
for email in &source.emails {
target.emails.insert(email.clone());
}
for url in &source.urls {
target.urls.insert(url.clone());
}
}
pub fn promote(set: &mut indexmap::IndexSet<String>, value: &str) {
if !set.contains(value) {
set.insert(value.to_owned());
}
if let Some(index) = set.get_index_of(value) {
set.move_index(index, 0);
}
}
pub fn register(registry: &mut Registry, name: &str, email: &str) -> String {
if let Some(contributor) = registry
.values_mut()
.find(|contributor| contributor.emails.contains(email))
{
if !name.is_empty() {
contributor.names.insert(name.to_owned());
}
return contributor.alias.clone();
}
let mut contributor = Contributor::new(email);
if !name.is_empty() {
contributor.add_name(name);
}
contributor.add_email(email);
registry.insert(email.to_owned(), contributor);
email.to_owned()
}
pub fn register_curated(
registry: &mut Registry,
alias: &str,
name: &str,
email: &str,
) -> sysexits::Result<()> {
if registry.contains_key(alias) {
eprintln!("git-harvest: {alias:?} is registered already");
return Err(sysexits::ExitCode::DataErr);
}
if let Some(owner) =
registry.values().find(|known| known.emails.contains(email))
{
eprintln!(
"git-harvest: <{email}> belongs to {:?} already; use `git \
harvest id update` or `git harvest id merge`",
owner.alias
);
return Err(sysexits::ExitCode::DataErr);
}
let mut contributor = Contributor::new(alias);
if !name.is_empty() {
contributor.add_name(name);
}
contributor.add_email(email);
registry.insert(alias.to_owned(), contributor);
Ok(())
}
pub fn absorb(
registry: &mut Registry,
incoming: &Registry,
) -> sysexits::Result<BTreeMap<String, String>> {
let mut moved = BTreeMap::new();
for (alias, contributor) in incoming {
let existing = registry
.values()
.find(|known| {
known
.emails
.iter()
.any(|email| contributor.emails.contains(email))
})
.map(|known| known.alias.clone());
let landed = if let Some(existing) = existing {
fold(
registry
.get_mut(&existing)
.expect("the match was just found"),
contributor,
);
existing
} else {
registry
.entry(alias.clone())
.and_modify(|known| fold(known, contributor))
.or_insert_with(|| contributor.clone());
alias.clone()
};
moved.insert(alias.clone(), landed);
}
consolidate(registry, &mut moved)?;
Ok(moved)
}
fn consolidate(
registry: &mut Registry,
moved: &mut BTreeMap<String, String>,
) -> sysexits::Result<()> {
while let Some((first, second, email)) = shared_email(registry) {
let curated_first = curated(®istry[&first]);
let curated_second = curated(®istry[&second]);
if curated_first && curated_second {
eprintln!(
"git-harvest: {first:?} and {second:?} both claim <{email}> \
and are each curated; run `git harvest id merge {first} \
{second} <alias>` to settle it"
);
return Err(sysexits::ExitCode::DataErr);
}
let (keep, drop) = if curated_second {
(second, first)
} else {
(first, second)
};
let taken = registry.remove(&drop).expect("the loser was just found");
fold(
registry.get_mut(&keep).expect("the winner was just found"),
&taken,
);
for landed in moved.values_mut() {
if *landed == drop {
landed.clone_from(&keep);
}
}
}
Ok(())
}
fn shared_email(registry: &Registry) -> Option<(String, String, String)> {
let contributors: Vec<&Contributor> = registry.values().collect();
for (index, first) in contributors.iter().enumerate() {
for second in &contributors[index + 1..] {
if let Some(email) = first
.emails
.iter()
.find(|email| second.emails.contains(email.as_str()))
{
return Some((
first.alias.clone(),
second.alias.clone(),
email.clone(),
));
}
}
}
None
}