Compare commits

..

1 commit

Author SHA1 Message Date
ec84e86659
read from an instance of GnuPG 2026-02-02 09:30:56 +01:00
4 changed files with 15 additions and 169 deletions

View file

@ -1,140 +0,0 @@
use std::{
fmt::Display,
fs::File,
io::{Read as _, Write as _},
path::PathBuf,
sync::LazyLock,
};
use serde::{Deserialize, Serialize};
use crate::cli::THEME;
pub static DATA_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
let mut dir = dirs::data_dir().expect("");
dir.push("git-identity");
dir
});
pub static IDENTITIES_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
let mut dir = DATA_FOLDER.clone();
dir.push("identities");
dir
});
#[derive(Debug)]
pub struct Identity {
pub alias: String,
pub data: IdentityData,
}
#[derive(thiserror::Error, Debug)]
pub enum IdentityOpenError {
#[error("IO error: {0}")]
IO(#[from] std::io::Error),
#[error("error while deserializing: {0}")]
Deserialize(#[from] toml::de::Error),
}
impl Identity {
pub fn open<A>(alias: &str) -> Result<Self, IdentityOpenError>
where
A: Into<String>,
{
let alias = String::from(alias);
let path = {
let mut path = DATA_FOLDER.clone();
path.push("identities");
path.push(&alias);
path
};
let mut file = File::options()
.read(true)
.write(false)
.create(false)
.open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let data = IdentityData::deserialize(toml::Deserializer::parse(&contents)?)?;
Ok(Self { alias, data })
}
pub fn get_path(&self) -> PathBuf {
let mut path = IDENTITIES_FOLDER.clone();
path.push(&self.alias);
path
}
pub fn save(&self) -> std::io::Result<()> {
let contents = toml::to_string(&self.data)
.expect("there should only be serializable values in the internal data structure");
let path = self.get_path();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
File::options()
.write(true)
.truncate(true)
.append(false)
.create(true)
.open(&path)?
.write_all(contents.as_bytes())?;
Ok(())
}
pub fn save_interactively(&self) -> anyhow::Result<()> {
let path = self.get_path();
if std::fs::exists(&path)?
&& !dialoguer::Confirm::with_theme(&*THEME)
.with_prompt(format!("{} already exists. Ovewrite?", path.display()))
.interact()?
{
Ok(())
} else {
Ok(self.save()?)
}
}
}
impl Display for Identity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\":", self.alias)?;
if let Some(ref name) = self.data.name {
write!(f, " {name}")?;
}
if let Some(ref email) = self.data.email {
write!(f, " <{email}>")?;
}
if let Some(ref sigkey) = self.data.sigkey {
write!(f, " (sigkey: {sigkey})")?;
}
if let Some(ref authkey) = self.data.authkey {
write!(f, " (authkey: {})", authkey.display())?;
}
Ok(())
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct IdentityData {
pub name: Option<String>,
pub email: Option<String>,
pub sigkey: Option<String>,
pub authkey: Option<PathBuf>,
}

View file

@ -1,7 +1,14 @@
//! This crate is not a library but we need some things to be exported so that they can be read by //! This crate is not a library but we need some things to be exported so that they can be read by
//! cargo xtasks //! cargo xtasks
use std::{path::PathBuf, sync::LazyLock};
pub mod cli; pub mod cli;
pub mod data;
pub mod logging; pub mod logging;
pub mod subcommands; pub mod subcommands;
pub static DATA_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
let mut dir = dirs::data_dir().expect("");
dir.push("git-identity");
dir
});

View file

@ -4,7 +4,7 @@ use tracing_subscriber::{Layer as _, layer::SubscriberExt as _, util::Subscriber
pub const DEFAULT_LOG_LEVEL: Level = if cfg!(debug_assertions) { pub const DEFAULT_LOG_LEVEL: Level = if cfg!(debug_assertions) {
Level::DEBUG // debug builds Level::DEBUG // debug builds
} else { } else {
Level::INFO // release builds Level::WARN // release builds
}; };
fn level_to_index(level: Level) -> u8 { fn level_to_index(level: Level) -> u8 {

View file

@ -25,27 +25,17 @@ pub fn main(_global_cli: crate::cli::GlobalArgs, cli: crate::cli::ImportCli) ->
.to_str() .to_str()
.expect("this should always be valid UTF-8"); .expect("this should always be valid UTF-8");
let alias = dialoguer::Input::<String>::with_theme(&*THEME) let identity_name = dialoguer::Input::<String>::with_theme(&*THEME)
.with_prompt("What should this identity be named?") .with_prompt("What should this identity be named?")
.interact_text()?; .interact_text()?;
let authkey: Option<PathBuf> = select_authkey(&mut ctx, &key, &alias)?; let authkey: Option<PathBuf> = select_authkey(&mut ctx, &key, &identity_name)?;
let (name, email) = select_name_and_email(&key)?; let (name, email) = select_name_and_email(&key)?;
let identity = crate::data::Identity { info!(
alias, "Imported identity: [{identity_name}] {name:?} <{email:?}> (signing key: {fingerprint}, authentication key: {authkey:?})"
data: crate::data::IdentityData { );
name,
email,
sigkey: Some(fingerprint.to_owned()),
authkey,
},
};
identity.save_interactively()?;
info!("Imported identity {identity}");
Ok(()) Ok(())
} }
@ -217,17 +207,6 @@ fn select_authkey(
) -> anyhow::Result<Option<PathBuf>> { ) -> anyhow::Result<Option<PathBuf>> {
let mut authkey_prompt = dialoguer::Input::<String>::with_theme(&*THEME) let mut authkey_prompt = dialoguer::Input::<String>::with_theme(&*THEME)
.with_prompt("Select an SSH key (leave empty for none)") .with_prompt("Select an SSH key (leave empty for none)")
.validate_with(|input: &String| -> Result<(), Cow<str>> {
if input.is_empty() {
return Ok(());
}
match std::fs::exists(input) {
Ok(true) => Ok(()),
Ok(false) => Err(Cow::Borrowed("file not found")),
Err(err) => Err(Cow::Owned(err.to_string())),
}
})
.allow_empty(true); .allow_empty(true);
if let Some(authkey) = try_export_as_ssh_key(ctx, key, identity_name)? { if let Some(authkey) = try_export_as_ssh_key(ctx, key, identity_name)? {
@ -269,7 +248,7 @@ fn try_export_as_ssh_key(
} }
let default_destination = { let default_destination = {
let mut path = crate::data::DATA_FOLDER.clone(); let mut path = crate::DATA_FOLDER.clone();
path.push("ssh"); path.push("ssh");
path.push(format!("{identity_name}.pub")); path.push(format!("{identity_name}.pub"));
path path