Compare commits
2 commits
ec84e86659
...
7bbeff1918
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bbeff1918 | |||
| a844ec48a4 |
4 changed files with 169 additions and 15 deletions
140
src/data.rs
Normal file
140
src/data.rs
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
//! 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
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -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::WARN // release builds
|
Level::INFO // release builds
|
||||||
};
|
};
|
||||||
|
|
||||||
fn level_to_index(level: Level) -> u8 {
|
fn level_to_index(level: Level) -> u8 {
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,27 @@ 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 identity_name = dialoguer::Input::<String>::with_theme(&*THEME)
|
let alias = 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, &identity_name)?;
|
let authkey: Option<PathBuf> = select_authkey(&mut ctx, &key, &alias)?;
|
||||||
|
|
||||||
let (name, email) = select_name_and_email(&key)?;
|
let (name, email) = select_name_and_email(&key)?;
|
||||||
|
|
||||||
info!(
|
let identity = crate::data::Identity {
|
||||||
"Imported identity: [{identity_name}] {name:?} <{email:?}> (signing key: {fingerprint}, authentication key: {authkey:?})"
|
alias,
|
||||||
);
|
data: crate::data::IdentityData {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
sigkey: Some(fingerprint.to_owned()),
|
||||||
|
authkey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
identity.save_interactively()?;
|
||||||
|
|
||||||
|
info!("Imported identity {identity}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -207,6 +217,17 @@ 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)? {
|
||||||
|
|
@ -248,7 +269,7 @@ fn try_export_as_ssh_key(
|
||||||
}
|
}
|
||||||
|
|
||||||
let default_destination = {
|
let default_destination = {
|
||||||
let mut path = crate::DATA_FOLDER.clone();
|
let mut path = crate::data::DATA_FOLDER.clone();
|
||||||
path.push("ssh");
|
path.push("ssh");
|
||||||
path.push(format!("{identity_name}.pub"));
|
path.push(format!("{identity_name}.pub"));
|
||||||
path
|
path
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue