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
|
||||
//! cargo xtasks
|
||||
|
||||
use std::{path::PathBuf, sync::LazyLock};
|
||||
|
||||
pub mod cli;
|
||||
pub mod data;
|
||||
pub mod logging;
|
||||
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) {
|
||||
Level::DEBUG // debug builds
|
||||
} else {
|
||||
Level::WARN // release builds
|
||||
Level::INFO // release builds
|
||||
};
|
||||
|
||||
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()
|
||||
.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?")
|
||||
.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)?;
|
||||
|
||||
info!(
|
||||
"Imported identity: [{identity_name}] {name:?} <{email:?}> (signing key: {fingerprint}, authentication key: {authkey:?})"
|
||||
);
|
||||
let identity = crate::data::Identity {
|
||||
alias,
|
||||
data: crate::data::IdentityData {
|
||||
name,
|
||||
email,
|
||||
sigkey: Some(fingerprint.to_owned()),
|
||||
authkey,
|
||||
},
|
||||
};
|
||||
|
||||
identity.save_interactively()?;
|
||||
|
||||
info!("Imported identity {identity}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -207,6 +217,17 @@ fn select_authkey(
|
|||
) -> anyhow::Result<Option<PathBuf>> {
|
||||
let mut authkey_prompt = dialoguer::Input::<String>::with_theme(&*THEME)
|
||||
.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);
|
||||
|
||||
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 mut path = crate::DATA_FOLDER.clone();
|
||||
let mut path = crate::data::DATA_FOLDER.clone();
|
||||
path.push("ssh");
|
||||
path.push(format!("{identity_name}.pub"));
|
||||
path
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue