summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorCyborus <87248184+Cyborus04@users.noreply.github.com>2023-08-19 04:40:46 +0200
committerCyborus <87248184+Cyborus04@users.noreply.github.com>2023-08-19 04:40:46 +0200
commit3dbbcb75a14523cb07bc3b23ad54f654d13da3a9 (patch)
tree795180d2b03fabb1afe98225cab8923eea3e0b92 /src
parentMerge pull request 'missed a bit in the key file move' (#8) from keys-file in... (diff)
downloadforgejo-api-3dbbcb75a14523cb07bc3b23ad54f654d13da3a9.tar.xz
forgejo-api-3dbbcb75a14523cb07bc3b23ad54f654d13da3a9.zip
rework key lookup
Now uses the current branch's remote to find the host, and only has one account signed in per host.
Diffstat (limited to 'src')
-rw-r--r--src/keys.rs147
-rw-r--r--src/main.rs117
2 files changed, 120 insertions, 144 deletions
diff --git a/src/keys.rs b/src/keys.rs
index 047eba1..bc57cc4 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -5,15 +5,10 @@ use url::Url;
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
pub struct KeyInfo {
- pub hosts: BTreeMap<String, HostInfo>,
- pub domain_to_name: BTreeMap<String, String>,
+ pub hosts: BTreeMap<String, LoginInfo>,
}
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> {
let path = directories::ProjectDirs::from("", "Cyborus", "forgejo-cli")
.ok_or_else(|| eyre!("Could not find data directory"))?
@@ -47,37 +42,78 @@ impl KeyInfo {
Ok(())
}
- pub async fn get_current_host_and_repo(&self) -> eyre::Result<(&str, &HostInfo, String)> {
- let remotes = get_remotes().await?;
- let remote = get_remote(&remotes).await?;
- let host_str = remote
+ pub fn get_current(&self) -> eyre::Result<(HostInfo<'_>, RepoInfo)> {
+ let repo = git2::Repository::open(".")?;
+ let remote_url = get_remote(&repo)?;
+ 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()
.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)
} else {
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
- .get_key_value(name)
+ .get(&domain)
.ok_or_else(|| eyre!("not signed in to {domain}"))?;
- Ok((name, host, repo_from_url(&remote)?.into()))
+ Ok(login_info)
}
+}
- pub async fn get_current_host(&self) -> eyre::Result<(&str, &HostInfo)> {
- let (name, host, _) = self.get_current_host_and_repo().await?;
- Ok((name, host))
+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())
}
- async fn get_current_user(&self) -> eyre::Result<(&str, &UserInfo)> {
- let user = self.get_current_host().await?.1.get_current_user()?;
+ pub fn url(&self) -> &Url {
+ &self.url
+ }
- Ok(user)
+ pub fn username(&self) -> &'a str {
+ &self.login_info.name
+ }
+}
+
+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)
}
-#[derive(serde::Serialize, serde::Deserialize, Clone)]
-pub struct HostInfo {
- pub default: Option<String>,
- pub url: Url,
- pub users: BTreeMap<String, UserInfo>,
+#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
+pub struct LoginInfo {
+ name: String,
+ key: String,
}
-impl HostInfo {
- pub fn get_current_user(&self) -> eyre::Result<(&str, &UserInfo)> {
- if self.users.len() == 1 {
- let (s, k) = self.users.first_key_value().unwrap();
- return Ok((s, k));
- }
- if let Some(default) = self.default.as_ref() {
- if let Some(default_info) = self.users.get(default) {
- return Ok((default, default_info));
- }
+impl LoginInfo {
+ pub fn new(name: String, key: String) -> Self {
+ Self {
+ name,
+ key,
}
-
- Err(eyre!("could not find user"))
}
-}
-#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
-pub struct UserInfo {
- pub name: String,
- pub key: String,
-}
+ pub fn username(&self) -> &str {
+ &self.name
+ }
-async fn get_remotes() -> eyre::Result<Vec<(String, Url)>> {
- 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)
+ pub fn api_for(&self, url: &Url) -> Result<forgejo_api::Forgejo, forgejo_api::ForgejoError> {
+ forgejo_api::Forgejo::new(&self.key, url.clone())
+ }
}
-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");
- };
+fn get_remote(repo: &git2::Repository) -> eyre::Result<Url> {
+ let head = repo.head()?;
+ let branch_name = head.name().ok_or_else(|| eyre!("branch name not UTF-8"))?;
+ 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)?;
+ let url = Url::parse(std::str::from_utf8(remote.url_bytes())?)?;
Ok(url)
}
diff --git a/src/main.rs b/src/main.rs
index 7e881ee..5255abc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -55,22 +55,12 @@ pub enum AuthCommand {
Login,
Logout {
host: String,
- user: String,
- },
- Switch {
- /// The host to set the default account for.
- #[clap(short, long)]
- host: Option<String>,
- user: String,
},
AddKey {
/// The domain name of the forgejo instance.
host: String,
/// The user that the key is associated with
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.
key: Option<String>,
},
@@ -93,13 +83,9 @@ async fn main() -> eyre::Result<()> {
set_upstream,
push,
} => {
- // let (host_domain, host_keys, repo) = keys.get_current_host_and_repo().await?;
- let host_info = keys
- .hosts
- .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 host = Url::parse(&host)?;
+ let login = keys.get_login(&host)?;
+ let api = login.api_for(&host)?;
let repo_spec = CreateRepoOption {
auto_init: false,
default_branch: "main".into(),
@@ -116,7 +102,7 @@ async fn main() -> eyre::Result<()> {
let new_repo = api.create_repo(repo_spec).await?;
eprintln!(
"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");
@@ -133,10 +119,9 @@ async fn main() -> eyre::Result<()> {
}
}
RepoCommand::Info => {
- let (_, host_keys, repo) = keys.get_current_host_and_repo().await?;
- let (_, user) = host_keys.get_current_user()?;
- let api = Forgejo::new(&user.key, host_keys.url.clone())?;
- let repo = api.get_repo(&user.name, &repo).await?;
+ let (host, repo) = keys.get_current()?;
+ let api = host.api()?;
+ let repo = api.get_repo(repo.owner(), repo.name()).await?;
match repo {
Some(repo) => {
dbg!(repo);
@@ -145,26 +130,32 @@ async fn main() -> eyre::Result<()> {
}
}
RepoCommand::Browse => {
- let (_, host_keys, repo) = keys.get_current_host_and_repo().await?;
- let (_, user) = host_keys.get_current_user()?;
- open::that(
- host_keys
- .url
- .join(&format!("/{}/{repo}", user.name))?
- .as_str(),
- )?;
+ let (host, repo) = keys.get_current()?;
+ let mut url = host.url().clone();
+ let new_path = format!("{}/{}/{}",
+ url.path()
+ .strip_suffix("/")
+ .unwrap_or(url.path()),
+ repo.owner(),
+ repo.name(),
+ );
+ url.set_path(&new_path);
+ open::that(url.as_str())?;
}
},
Command::User { host } => {
- let (_, host_keys) = match host.as_deref() {
- Some(s) => (
- s,
- keys.hosts.get(s).ok_or_else(|| eyre!("not a known host"))?,
+ let host = host.map(|host| Url::parse(&host)).transpose()?;
+ let (url, name) = match host {
+ Some(url) => (
+ 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 {}@{}", info.name, host_keys.url);
+ eprintln!("currently signed in to {name}@{url}");
}
Command::Auth(auth_subcommand) => match auth_subcommand {
AuthCommand::Login => {
@@ -172,57 +163,30 @@ async fn main() -> eyre::Result<()> {
// let user = readline("username: ").await?;
// let pass = readline("password: ").await?;
}
- AuthCommand::Logout { host, user } => {
- let was_signed_in = keys
+ AuthCommand::Logout { host } => {
+ let info_opt = keys
.hosts
- .get_mut(&host)
- .and_then(|host| host.users.remove(&user))
- .is_some();
- if was_signed_in {
- eprintln!("signed out of {user}@{host}");
+ .remove(&host);
+ if let Some(info) = info_opt {
+ eprintln!("signed out of {}@{}", &info.username(), host);
} else {
- eprintln!("already not signed in");
- }
- }
- 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!(),
+ eprintln!("already not signed in to {host}");
}
}
AuthCommand::AddKey {
host,
user,
- name,
key,
} => {
- let host_keys = keys
- .hosts
- .get_mut(&host)
- .ok_or_else(|| eyre!("unknown host {host}"))?;
let key = match key {
Some(key) => key,
None => readline("new key: ").await?,
};
- if host_keys.users.get(&user).is_none() {
- host_keys.users.insert(
- name.unwrap_or_else(|| user.clone()),
- UserInfo { name: user, key },
- );
+ if keys.hosts.get(&user).is_none() {
+ keys.hosts.insert(host, LoginInfo::new(user, key));
} else {
println!(
- "key {} for {} already exists (rename it?)",
- name.unwrap_or(user),
+ "key for {} already exists",
host
);
}
@@ -231,11 +195,8 @@ async fn main() -> eyre::Result<()> {
if keys.hosts.is_empty() {
println!("No logins.");
}
- for (host_url, host_info) in &keys.hosts {
- for (key_name, key_info) in &host_info.users {
- let UserInfo { name, key: _ } = key_info;
- println!("{key_name}: {name}@{host_url}");
- }
+ for (host_url, login_info) in &keys.hosts {
+ println!("{}@{}", login_info.username(), host_url);
}
}
},