src/identity.rs
Ref: Size: 5.9 KiB
use std::fs;
use std::path::PathBuf;
use base64::Engine;
use git2::Repository;
use crate::error::Error;
use crate::event::Author;
use crate::signing;
pub fn get_author(repo: &Repository) -> Result<Author, git2::Error> {
let config = repo.config()?;
let name = config.get_string("user.name")?;
let email = config.get_string("user.email")?;
Ok(Author { name, email })
}
pub fn author_signature(author: &Author) -> Result<git2::Signature<'static>, git2::Error> {
git2::Signature::now(&author.name, &author.email)
}
/// Path to the identity-aliases file inside the repo's collab directory.
fn aliases_path(repo: &Repository) -> PathBuf {
repo.path().join("collab").join("identity-aliases")
}
/// Load the list of email aliases from `.git/collab/identity-aliases`.
/// Returns an empty vec if the file doesn't exist.
pub fn load_aliases(repo: &Repository) -> Result<Vec<String>, Error> {
let path = aliases_path(repo);
if !path.exists() {
return Ok(Vec::new());
}
let contents = fs::read_to_string(&path)?;
let aliases: Vec<String> = contents
.lines()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty())
.collect();
Ok(aliases)
}
/// Save the list of email aliases to `.git/collab/identity-aliases`.
fn save_aliases(repo: &Repository, aliases: &[String]) -> Result<(), Error> {
let path = aliases_path(repo);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let contents = aliases.join("\n");
let contents = if contents.is_empty() {
contents
} else {
format!("{}\n", contents)
};
fs::write(&path, contents)?;
Ok(())
}
/// Add an email alias for the current identity. Returns true if added, false if already present.
pub fn add_alias(repo: &Repository, email: &str) -> Result<bool, Error> {
let author = get_author(repo)?;
if email == author.email {
return Err(Error::Cmd(format!(
"'{}' is already your primary email; cannot add as alias",
email
)));
}
let mut aliases = load_aliases(repo)?;
if aliases.iter().any(|a| a == email) {
return Ok(false);
}
aliases.push(email.to_string());
save_aliases(repo, &aliases)?;
Ok(true)
}
/// Remove an email alias. Returns an error if the alias is not found.
pub fn remove_alias(repo: &Repository, email: &str) -> Result<(), Error> {
let mut aliases = load_aliases(repo)?;
let before = aliases.len();
aliases.retain(|a| a != email);
if aliases.len() == before {
return Err(Error::Cmd(format!(
"alias '{}' not found",
email
)));
}
save_aliases(repo, &aliases)?;
Ok(())
}
/// Display full identity information: name, email, signing key, aliases.
pub fn whoami(repo: &Repository) -> Result<String, Error> {
let author = get_author(repo)?;
let mut lines = Vec::new();
lines.push(format!("Name: {}", author.name));
lines.push(format!("Email: {}", author.email));
// Signing key
match signing::signing_key_dir() {
Ok(config_dir) => match signing::load_verifying_key(&config_dir) {
Ok(vk) => {
let pubkey_b64 =
base64::engine::general_purpose::STANDARD.encode(vk.to_bytes());
lines.push(format!("Signing key: {}", pubkey_b64));
}
Err(_) => {
lines.push("Signing key: (not configured)".to_string());
}
},
Err(_) => {
lines.push("Signing key: (not configured)".to_string());
}
}
// Aliases
let aliases = load_aliases(repo)?;
if aliases.is_empty() {
lines.push("Aliases: (none)".to_string());
} else {
lines.push("Aliases:".to_string());
for alias in &aliases {
lines.push(format!(" {}", alias));
}
}
Ok(lines.join("\n"))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn test_repo(name: &str, email: &str) -> (TempDir, Repository) {
let dir = TempDir::new().unwrap();
let repo = Repository::init(dir.path()).unwrap();
{
let mut config = repo.config().unwrap();
config.set_str("user.name", name).unwrap();
config.set_str("user.email", email).unwrap();
}
(dir, repo)
}
#[test]
fn test_load_aliases_empty_when_no_file() {
let (_dir, repo) = test_repo("Test", "test@example.com");
let aliases = load_aliases(&repo).unwrap();
assert!(aliases.is_empty());
}
#[test]
fn test_add_and_load_alias() {
let (_dir, repo) = test_repo("Test", "test@example.com");
let added = add_alias(&repo, "other@example.com").unwrap();
assert!(added);
let aliases = load_aliases(&repo).unwrap();
assert_eq!(aliases, vec!["other@example.com"]);
}
#[test]
fn test_add_duplicate_alias_returns_false() {
let (_dir, repo) = test_repo("Test", "test@example.com");
add_alias(&repo, "other@example.com").unwrap();
let added = add_alias(&repo, "other@example.com").unwrap();
assert!(!added);
}
#[test]
fn test_add_primary_email_errors() {
let (_dir, repo) = test_repo("Test", "test@example.com");
let result = add_alias(&repo, "test@example.com");
assert!(result.is_err());
}
#[test]
fn test_remove_alias() {
let (_dir, repo) = test_repo("Test", "test@example.com");
add_alias(&repo, "other@example.com").unwrap();
remove_alias(&repo, "other@example.com").unwrap();
let aliases = load_aliases(&repo).unwrap();
assert!(aliases.is_empty());
}
#[test]
fn test_remove_nonexistent_alias_errors() {
let (_dir, repo) = test_repo("Test", "test@example.com");
let result = remove_alias(&repo, "nobody@example.com");
assert!(result.is_err());
}
}