mirror of
https://codeberg.org/Cyborus/forgejo-cli.git
synced 2024-11-10 12:09:33 +01:00
rework key lookup
Now uses the current branch's remote to find the host, and only has one account signed in per host.
This commit is contained in:
parent
7b5dcb8d65
commit
3dbbcb75a1
2 changed files with 121 additions and 145 deletions
149
src/keys.rs
149
src/keys.rs
|
@ -5,15 +5,10 @@ use url::Url;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
|
||||||
pub struct KeyInfo {
|
pub struct KeyInfo {
|
||||||
pub hosts: BTreeMap<String, HostInfo>,
|
pub hosts: BTreeMap<String, LoginInfo>,
|
||||||
pub domain_to_name: BTreeMap<String, String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyInfo {
|
impl KeyInfo {
|
||||||
fn domain_to_name(&self, domain: &str) -> Option<&str> {
|
|
||||||
self.domain_to_name.get(domain).map(|s| &**s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn load() -> eyre::Result<Self> {
|
pub async fn load() -> eyre::Result<Self> {
|
||||||
let path = directories::ProjectDirs::from("", "Cyborus", "forgejo-cli")
|
let path = directories::ProjectDirs::from("", "Cyborus", "forgejo-cli")
|
||||||
.ok_or_else(|| eyre!("Could not find data directory"))?
|
.ok_or_else(|| eyre!("Could not find data directory"))?
|
||||||
|
@ -47,37 +42,78 @@ impl KeyInfo {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_current_host_and_repo(&self) -> eyre::Result<(&str, &HostInfo, String)> {
|
pub fn get_current(&self) -> eyre::Result<(HostInfo<'_>, RepoInfo)> {
|
||||||
let remotes = get_remotes().await?;
|
let repo = git2::Repository::open(".")?;
|
||||||
let remote = get_remote(&remotes).await?;
|
let remote_url = get_remote(&repo)?;
|
||||||
let host_str = remote
|
let login_info = self.get_login(&remote_url)?;
|
||||||
|
|
||||||
|
let mut path = remote_url.path_segments().ok_or_else(|| eyre!("bad path"))?.collect::<Vec<_>>();
|
||||||
|
let repo_name = path.pop().ok_or_else(|| eyre!("path does not have repo name"))?.to_string();
|
||||||
|
let owner = path.pop().ok_or_else(|| eyre!("path does not have owner name"))?.to_string();
|
||||||
|
let base_path = path.join("/");
|
||||||
|
|
||||||
|
let mut url = remote_url;
|
||||||
|
url.set_path(&base_path);
|
||||||
|
let host_info = HostInfo {
|
||||||
|
url,
|
||||||
|
login_info,
|
||||||
|
};
|
||||||
|
let repo_info = RepoInfo {
|
||||||
|
owner,
|
||||||
|
name: repo_name,
|
||||||
|
};
|
||||||
|
Ok((host_info, repo_info))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_login(&self, url: &Url) -> eyre::Result<&LoginInfo> {
|
||||||
|
let host_str = url
|
||||||
.host_str()
|
.host_str()
|
||||||
.ok_or_else(|| eyre!("remote url does not have host"))?;
|
.ok_or_else(|| eyre!("remote url does not have host"))?;
|
||||||
let domain = if let Some(port) = remote.port() {
|
let domain = if let Some(port) = url.port() {
|
||||||
format!("{}:{}", host_str, port)
|
format!("{}:{}", host_str, port)
|
||||||
} else {
|
} else {
|
||||||
host_str.to_owned()
|
host_str.to_owned()
|
||||||
};
|
};
|
||||||
let name = self
|
|
||||||
.domain_to_name(&domain)
|
|
||||||
.ok_or_else(|| eyre!("unknown remote"))?;
|
|
||||||
|
|
||||||
let (name, host) = self
|
let login_info = self
|
||||||
.hosts
|
.hosts
|
||||||
.get_key_value(name)
|
.get(&domain)
|
||||||
.ok_or_else(|| eyre!("not signed in to {domain}"))?;
|
.ok_or_else(|| eyre!("not signed in to {domain}"))?;
|
||||||
Ok((name, host, repo_from_url(&remote)?.into()))
|
Ok(login_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HostInfo<'a> {
|
||||||
|
url: Url,
|
||||||
|
login_info: &'a LoginInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> HostInfo<'a> {
|
||||||
|
pub fn api(&self) -> Result<forgejo_api::Forgejo, forgejo_api::ForgejoError> {
|
||||||
|
self.login_info.api_for(self.url())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_current_host(&self) -> eyre::Result<(&str, &HostInfo)> {
|
pub fn url(&self) -> &Url {
|
||||||
let (name, host, _) = self.get_current_host_and_repo().await?;
|
&self.url
|
||||||
Ok((name, host))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_current_user(&self) -> eyre::Result<(&str, &UserInfo)> {
|
pub fn username(&self) -> &'a str {
|
||||||
let user = self.get_current_host().await?.1.get_current_user()?;
|
&self.login_info.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(user)
|
pub struct RepoInfo {
|
||||||
|
owner: String,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepoInfo {
|
||||||
|
pub fn owner(&self) -> &str {
|
||||||
|
&self.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,57 +136,36 @@ fn repo_from_url(url: &Url) -> eyre::Result<&str> {
|
||||||
Ok(repo)
|
Ok(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
|
||||||
pub struct HostInfo {
|
pub struct LoginInfo {
|
||||||
pub default: Option<String>,
|
name: String,
|
||||||
pub url: Url,
|
key: String,
|
||||||
pub users: BTreeMap<String, UserInfo>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostInfo {
|
impl LoginInfo {
|
||||||
pub fn get_current_user(&self) -> eyre::Result<(&str, &UserInfo)> {
|
pub fn new(name: String, key: String) -> Self {
|
||||||
if self.users.len() == 1 {
|
Self {
|
||||||
let (s, k) = self.users.first_key_value().unwrap();
|
name,
|
||||||
return Ok((s, k));
|
key,
|
||||||
}
|
|
||||||
if let Some(default) = self.default.as_ref() {
|
|
||||||
if let Some(default_info) = self.users.get(default) {
|
|
||||||
return Ok((default, default_info));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Err(eyre!("could not find user"))
|
pub fn username(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn api_for(&self, url: &Url) -> Result<forgejo_api::Forgejo, forgejo_api::ForgejoError> {
|
||||||
|
forgejo_api::Forgejo::new(&self.key, url.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
|
fn get_remote(repo: &git2::Repository) -> eyre::Result<Url> {
|
||||||
pub struct UserInfo {
|
let head = repo.head()?;
|
||||||
pub name: String,
|
let branch_name = head.name().ok_or_else(|| eyre!("branch name not UTF-8"))?;
|
||||||
pub key: String,
|
let remote_name= repo.branch_upstream_remote(branch_name)?;
|
||||||
}
|
let remote_name = remote_name.as_str().ok_or_else(|| eyre!("remote name not UTF-8"))?;
|
||||||
|
let remote = repo.find_remote(remote_name)?;
|
||||||
async fn get_remotes() -> eyre::Result<Vec<(String, Url)>> {
|
let url = Url::parse(std::str::from_utf8(remote.url_bytes())?)?;
|
||||||
let repo = git2::Repository::open(".")?;
|
|
||||||
let remotes = repo
|
|
||||||
.remotes()?
|
|
||||||
.iter()
|
|
||||||
.filter_map(|name| {
|
|
||||||
let name = name?.to_string();
|
|
||||||
let url = Url::parse(repo.find_remote(&name).ok()?.url()?).ok()?;
|
|
||||||
Some((name, url))
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Ok(remotes)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_remote(remotes: &[(String, Url)]) -> eyre::Result<Url> {
|
|
||||||
let url = if remotes.len() == 1 {
|
|
||||||
remotes[0].1.clone()
|
|
||||||
} else if let Some((_, url)) = remotes.iter().find(|(name, _)| *name == "origin") {
|
|
||||||
url.clone()
|
|
||||||
} else {
|
|
||||||
eyre::bail!("could not find remote");
|
|
||||||
};
|
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
117
src/main.rs
117
src/main.rs
|
@ -55,22 +55,12 @@ pub enum AuthCommand {
|
||||||
Login,
|
Login,
|
||||||
Logout {
|
Logout {
|
||||||
host: String,
|
host: String,
|
||||||
user: String,
|
|
||||||
},
|
|
||||||
Switch {
|
|
||||||
/// The host to set the default account for.
|
|
||||||
#[clap(short, long)]
|
|
||||||
host: Option<String>,
|
|
||||||
user: String,
|
|
||||||
},
|
},
|
||||||
AddKey {
|
AddKey {
|
||||||
/// The domain name of the forgejo instance.
|
/// The domain name of the forgejo instance.
|
||||||
host: String,
|
host: String,
|
||||||
/// The user that the key is associated with
|
/// The user that the key is associated with
|
||||||
user: String,
|
user: String,
|
||||||
/// The name of the key. If not present, defaults to the username.
|
|
||||||
#[clap(short, long)]
|
|
||||||
name: Option<String>,
|
|
||||||
/// The key to add. If not present, the key will be read in from stdin.
|
/// The key to add. If not present, the key will be read in from stdin.
|
||||||
key: Option<String>,
|
key: Option<String>,
|
||||||
},
|
},
|
||||||
|
@ -93,13 +83,9 @@ async fn main() -> eyre::Result<()> {
|
||||||
set_upstream,
|
set_upstream,
|
||||||
push,
|
push,
|
||||||
} => {
|
} => {
|
||||||
// let (host_domain, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
let host = Url::parse(&host)?;
|
||||||
let host_info = keys
|
let login = keys.get_login(&host)?;
|
||||||
.hosts
|
let api = login.api_for(&host)?;
|
||||||
.get(&host)
|
|
||||||
.ok_or_else(|| eyre!("not a known host"))?;
|
|
||||||
let (_, user) = host_info.get_current_user()?;
|
|
||||||
let api = Forgejo::new(&user.key, host_info.url.clone())?;
|
|
||||||
let repo_spec = CreateRepoOption {
|
let repo_spec = CreateRepoOption {
|
||||||
auto_init: false,
|
auto_init: false,
|
||||||
default_branch: "main".into(),
|
default_branch: "main".into(),
|
||||||
|
@ -116,7 +102,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
let new_repo = api.create_repo(repo_spec).await?;
|
let new_repo = api.create_repo(repo_spec).await?;
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"created new repo at {}",
|
"created new repo at {}",
|
||||||
host_info.url.join(&format!("{}/{}", user.name, repo))?
|
host.join(&format!("{}/{}", login.username(), repo))?
|
||||||
);
|
);
|
||||||
|
|
||||||
let upstream = set_upstream.as_deref().unwrap_or("origin");
|
let upstream = set_upstream.as_deref().unwrap_or("origin");
|
||||||
|
@ -133,10 +119,9 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RepoCommand::Info => {
|
RepoCommand::Info => {
|
||||||
let (_, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
let (host, repo) = keys.get_current()?;
|
||||||
let (_, user) = host_keys.get_current_user()?;
|
let api = host.api()?;
|
||||||
let api = Forgejo::new(&user.key, host_keys.url.clone())?;
|
let repo = api.get_repo(repo.owner(), repo.name()).await?;
|
||||||
let repo = api.get_repo(&user.name, &repo).await?;
|
|
||||||
match repo {
|
match repo {
|
||||||
Some(repo) => {
|
Some(repo) => {
|
||||||
dbg!(repo);
|
dbg!(repo);
|
||||||
|
@ -145,26 +130,32 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RepoCommand::Browse => {
|
RepoCommand::Browse => {
|
||||||
let (_, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
let (host, repo) = keys.get_current()?;
|
||||||
let (_, user) = host_keys.get_current_user()?;
|
let mut url = host.url().clone();
|
||||||
open::that(
|
let new_path = format!("{}/{}/{}",
|
||||||
host_keys
|
url.path()
|
||||||
.url
|
.strip_suffix("/")
|
||||||
.join(&format!("/{}/{repo}", user.name))?
|
.unwrap_or(url.path()),
|
||||||
.as_str(),
|
repo.owner(),
|
||||||
)?;
|
repo.name(),
|
||||||
|
);
|
||||||
|
url.set_path(&new_path);
|
||||||
|
open::that(url.as_str())?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Command::User { host } => {
|
Command::User { host } => {
|
||||||
let (_, host_keys) = match host.as_deref() {
|
let host = host.map(|host| Url::parse(&host)).transpose()?;
|
||||||
Some(s) => (
|
let (url, name) = match host {
|
||||||
s,
|
Some(url) => (
|
||||||
keys.hosts.get(s).ok_or_else(|| eyre!("not a known host"))?,
|
keys.get_login(&url)?.username(),
|
||||||
|
url,
|
||||||
),
|
),
|
||||||
None => keys.get_current_host().await?,
|
None => {
|
||||||
|
let (host, _) = keys.get_current()?;
|
||||||
|
(host.username(), host.url().clone())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let (_, info) = host_keys.get_current_user()?;
|
eprintln!("currently signed in to {name}@{url}");
|
||||||
eprintln!("currently signed in to {}@{}", info.name, host_keys.url);
|
|
||||||
}
|
}
|
||||||
Command::Auth(auth_subcommand) => match auth_subcommand {
|
Command::Auth(auth_subcommand) => match auth_subcommand {
|
||||||
AuthCommand::Login => {
|
AuthCommand::Login => {
|
||||||
|
@ -172,57 +163,30 @@ async fn main() -> eyre::Result<()> {
|
||||||
// let user = readline("username: ").await?;
|
// let user = readline("username: ").await?;
|
||||||
// let pass = readline("password: ").await?;
|
// let pass = readline("password: ").await?;
|
||||||
}
|
}
|
||||||
AuthCommand::Logout { host, user } => {
|
AuthCommand::Logout { host } => {
|
||||||
let was_signed_in = keys
|
let info_opt = keys
|
||||||
.hosts
|
.hosts
|
||||||
.get_mut(&host)
|
.remove(&host);
|
||||||
.and_then(|host| host.users.remove(&user))
|
if let Some(info) = info_opt {
|
||||||
.is_some();
|
eprintln!("signed out of {}@{}", &info.username(), host);
|
||||||
if was_signed_in {
|
|
||||||
eprintln!("signed out of {user}@{host}");
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!("already not signed in");
|
eprintln!("already not signed in to {host}");
|
||||||
}
|
|
||||||
}
|
|
||||||
AuthCommand::Switch { host, user } => {
|
|
||||||
let host = host.unwrap_or(keys.get_current_host().await?.0.to_string());
|
|
||||||
let host_info = keys
|
|
||||||
.hosts
|
|
||||||
.get_mut(&host)
|
|
||||||
.ok_or_else(|| eyre!("not a known host"))?;
|
|
||||||
if !host_info.users.contains_key(&user) {
|
|
||||||
bail!("could not switch user: not signed into {host} as {user}");
|
|
||||||
}
|
|
||||||
let previous = host_info.default.replace(user.clone());
|
|
||||||
print!("set current user for {host} to {user}");
|
|
||||||
match previous {
|
|
||||||
Some(prev) => println!(" (previously {prev})"),
|
|
||||||
None => println!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AuthCommand::AddKey {
|
AuthCommand::AddKey {
|
||||||
host,
|
host,
|
||||||
user,
|
user,
|
||||||
name,
|
|
||||||
key,
|
key,
|
||||||
} => {
|
} => {
|
||||||
let host_keys = keys
|
|
||||||
.hosts
|
|
||||||
.get_mut(&host)
|
|
||||||
.ok_or_else(|| eyre!("unknown host {host}"))?;
|
|
||||||
let key = match key {
|
let key = match key {
|
||||||
Some(key) => key,
|
Some(key) => key,
|
||||||
None => readline("new key: ").await?,
|
None => readline("new key: ").await?,
|
||||||
};
|
};
|
||||||
if host_keys.users.get(&user).is_none() {
|
if keys.hosts.get(&user).is_none() {
|
||||||
host_keys.users.insert(
|
keys.hosts.insert(host, LoginInfo::new(user, key));
|
||||||
name.unwrap_or_else(|| user.clone()),
|
|
||||||
UserInfo { name: user, key },
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"key {} for {} already exists (rename it?)",
|
"key for {} already exists",
|
||||||
name.unwrap_or(user),
|
|
||||||
host
|
host
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -231,11 +195,8 @@ async fn main() -> eyre::Result<()> {
|
||||||
if keys.hosts.is_empty() {
|
if keys.hosts.is_empty() {
|
||||||
println!("No logins.");
|
println!("No logins.");
|
||||||
}
|
}
|
||||||
for (host_url, host_info) in &keys.hosts {
|
for (host_url, login_info) in &keys.hosts {
|
||||||
for (key_name, key_info) in &host_info.users {
|
println!("{}@{}", login_info.username(), host_url);
|
||||||
let UserInfo { name, key: _ } = key_info;
|
|
||||||
println!("{key_name}: {name}@{host_url}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue