mirror of
https://codeberg.org/Cyborus/forgejo-cli.git
synced 2024-11-10 12:09:33 +01:00
Merge pull request 'host aliases' (#125) from alias-host into main
Reviewed-on: https://codeberg.org/Cyborus/forgejo-cli/pulls/125
This commit is contained in:
commit
74d3748fa8
9 changed files with 92 additions and 28 deletions
51
src/auth.rs
51
src/auth.rs
|
@ -26,7 +26,7 @@ impl AuthCommand {
|
||||||
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
AuthCommand::Login => {
|
AuthCommand::Login => {
|
||||||
let repo_info = crate::repo::RepoInfo::get_current(host_name, None, None)?;
|
let repo_info = crate::repo::RepoInfo::get_current(host_name, None, None, &keys)?;
|
||||||
let host_url = repo_info.host_url();
|
let host_url = repo_info.host_url();
|
||||||
let client_info = get_client_info_for(host_url);
|
let client_info = get_client_info_for(host_url);
|
||||||
if let Some((client_id, _)) = client_info {
|
if let Some((client_id, _)) = client_info {
|
||||||
|
@ -52,7 +52,7 @@ impl AuthCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AuthCommand::AddKey { user, key } => {
|
AuthCommand::AddKey { user, key } => {
|
||||||
let repo_info = crate::repo::RepoInfo::get_current(host_name, None, None)?;
|
let repo_info = crate::repo::RepoInfo::get_current(host_name, None, None, &keys)?;
|
||||||
let host_url = repo_info.host_url();
|
let host_url = repo_info.host_url();
|
||||||
let key = match key {
|
let key = match key {
|
||||||
Some(key) => key,
|
Some(key) => key,
|
||||||
|
@ -60,13 +60,12 @@ impl AuthCommand {
|
||||||
};
|
};
|
||||||
let host = crate::host_with_port(&host_url);
|
let host = crate::host_with_port(&host_url);
|
||||||
if !keys.hosts.contains_key(host) {
|
if !keys.hosts.contains_key(host) {
|
||||||
keys.hosts.insert(
|
let mut login = crate::keys::LoginInfo::Application {
|
||||||
host.to_owned(),
|
|
||||||
crate::keys::LoginInfo::Application {
|
|
||||||
name: user,
|
name: user,
|
||||||
token: key,
|
token: key,
|
||||||
},
|
};
|
||||||
);
|
add_ssh_alias(&mut login, host_url, keys).await;
|
||||||
|
keys.hosts.insert(host.to_owned(), login);
|
||||||
} else {
|
} else {
|
||||||
println!("key for {host} already exists");
|
println!("key for {host} already exists");
|
||||||
}
|
}
|
||||||
|
@ -168,12 +167,13 @@ async fn oauth_login(
|
||||||
// expires. Better to refresh slightly too soon than slightly too late.
|
// expires. Better to refresh slightly too soon than slightly too late.
|
||||||
let expires_in = std::time::Duration::from_secs(response.expires_in.saturating_sub(60) as u64);
|
let expires_in = std::time::Duration::from_secs(response.expires_in.saturating_sub(60) as u64);
|
||||||
let expires_at = time::OffsetDateTime::now_utc() + expires_in;
|
let expires_at = time::OffsetDateTime::now_utc() + expires_in;
|
||||||
let login_info = crate::keys::LoginInfo::OAuth {
|
let mut login_info = crate::keys::LoginInfo::OAuth {
|
||||||
name,
|
name,
|
||||||
token: response.access_token,
|
token: response.access_token,
|
||||||
refresh_token: response.refresh_token,
|
refresh_token: response.refresh_token,
|
||||||
expires_at,
|
expires_at,
|
||||||
};
|
};
|
||||||
|
add_ssh_alias(&mut login_info, host, keys).await;
|
||||||
let domain = crate::host_with_port(&host);
|
let domain = crate::host_with_port(&host);
|
||||||
keys.hosts.insert(domain.to_owned(), login_info);
|
keys.hosts.insert(domain.to_owned(), login_info);
|
||||||
|
|
||||||
|
@ -232,3 +232,38 @@ fn auth_server() -> (
|
||||||
});
|
});
|
||||||
(handle, rx)
|
(handle, rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn add_ssh_alias(
|
||||||
|
login: &mut crate::keys::LoginInfo,
|
||||||
|
host_url: &url::Url,
|
||||||
|
keys: &mut crate::keys::KeyInfo,
|
||||||
|
) {
|
||||||
|
let api = match login.api_for(host_url).await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
if let Some(ssh_url) = get_instance_ssh_url(api).await {
|
||||||
|
let http_host = crate::host_with_port(&host_url);
|
||||||
|
let ssh_host = crate::host_with_port(&ssh_url);
|
||||||
|
if http_host != ssh_host {
|
||||||
|
keys.aliases
|
||||||
|
.insert(ssh_host.to_string(), http_host.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_instance_ssh_url(api: forgejo_api::Forgejo) -> Option<url::Url> {
|
||||||
|
let query = forgejo_api::structs::RepoSearchQuery {
|
||||||
|
limit: Some(1),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let results = api.repo_search(query).await.ok()?;
|
||||||
|
if let Some(mut repos) = results.data {
|
||||||
|
if let Some(repo) = repos.pop() {
|
||||||
|
if let Some(ssh_url) = repo.ssh_url {
|
||||||
|
return Some(ssh_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ pub enum ViewCommand {
|
||||||
impl IssueCommand {
|
impl IssueCommand {
|
||||||
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
||||||
use IssueSubcommand::*;
|
use IssueSubcommand::*;
|
||||||
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref())?;
|
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref(), &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let repo = repo.name().ok_or_else(|| self.no_repo_error())?;
|
let repo = repo.name().ok_or_else(|| self.no_repo_error())?;
|
||||||
match self.command {
|
match self.command {
|
||||||
|
|
17
src/keys.rs
17
src/keys.rs
|
@ -6,6 +6,8 @@ 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, LoginInfo>,
|
pub hosts: BTreeMap<String, LoginInfo>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub aliases: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyInfo {
|
impl KeyInfo {
|
||||||
|
@ -54,6 +56,21 @@ impl KeyInfo {
|
||||||
pub async fn get_api(&mut self, url: &Url) -> eyre::Result<forgejo_api::Forgejo> {
|
pub async fn get_api(&mut self, url: &Url) -> eyre::Result<forgejo_api::Forgejo> {
|
||||||
self.get_login(url)?.api_for(url).await.map_err(Into::into)
|
self.get_login(url)?.api_for(url).await.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deref_alias(&self, url: url::Url) -> url::Url {
|
||||||
|
match self.aliases.get(crate::host_with_port(&url)) {
|
||||||
|
Some(replacement) => {
|
||||||
|
let s = format!(
|
||||||
|
"{}{}{}",
|
||||||
|
&url[..url::Position::BeforeHost],
|
||||||
|
replacement,
|
||||||
|
&url[url::Position::AfterPort..]
|
||||||
|
);
|
||||||
|
url::Url::parse(&s).unwrap()
|
||||||
|
}
|
||||||
|
None => url,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
|
|
|
@ -65,7 +65,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
Command::Pr(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
Command::Pr(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
||||||
Command::Wiki(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
Command::Wiki(subcommand) => subcommand.run(&mut keys, host_name).await?,
|
||||||
Command::WhoAmI { remote } => {
|
Command::WhoAmI { remote } => {
|
||||||
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
|
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref(), &keys)
|
||||||
.wrap_err("could not find host, try specifying with --host")?
|
.wrap_err("could not find host, try specifying with --host")?
|
||||||
.host_url()
|
.host_url()
|
||||||
.clone();
|
.clone();
|
||||||
|
|
|
@ -264,7 +264,7 @@ pub enum ViewCommand {
|
||||||
impl PrCommand {
|
impl PrCommand {
|
||||||
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
||||||
use PrSubcommand::*;
|
use PrSubcommand::*;
|
||||||
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref())?;
|
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref(), &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let repo = repo.name().ok_or_else(|| self.no_repo_error())?;
|
let repo = repo.name().ok_or_else(|| self.no_repo_error())?;
|
||||||
match self.command {
|
match self.command {
|
||||||
|
|
|
@ -126,7 +126,12 @@ pub enum AssetCommand {
|
||||||
|
|
||||||
impl ReleaseCommand {
|
impl ReleaseCommand {
|
||||||
pub async fn run(self, keys: &mut KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> {
|
pub async fn run(self, keys: &mut KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> {
|
||||||
let repo = RepoInfo::get_current(remote_name, self.repo.as_ref(), self.remote.as_deref())?;
|
let repo = RepoInfo::get_current(
|
||||||
|
remote_name,
|
||||||
|
self.repo.as_ref(),
|
||||||
|
self.remote.as_deref(),
|
||||||
|
&keys,
|
||||||
|
)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let repo = repo
|
let repo = repo
|
||||||
.name()
|
.name()
|
||||||
|
|
31
src/repo.rs
31
src/repo.rs
|
@ -17,6 +17,7 @@ impl RepoInfo {
|
||||||
host: Option<&str>,
|
host: Option<&str>,
|
||||||
repo: Option<&RepoArg>,
|
repo: Option<&RepoArg>,
|
||||||
remote: Option<&str>,
|
remote: Option<&str>,
|
||||||
|
keys: &crate::keys::KeyInfo,
|
||||||
) -> eyre::Result<Self> {
|
) -> eyre::Result<Self> {
|
||||||
// l = domain/owner/name
|
// l = domain/owner/name
|
||||||
// s = owner/name
|
// s = owner/name
|
||||||
|
@ -53,6 +54,7 @@ impl RepoInfo {
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|x| !x.cannot_be_a_base())
|
.filter(|x| !x.cannot_be_a_base())
|
||||||
.or_else(|| Url::parse(&format!("https://{host}/")).ok())
|
.or_else(|| Url::parse(&format!("https://{host}/")).ok())
|
||||||
|
.map(|url| keys.deref_alias(url))
|
||||||
}
|
}
|
||||||
repo_name = Some(RepoName {
|
repo_name = Some(RepoName {
|
||||||
owner: repo.owner.clone(),
|
owner: repo.owner.clone(),
|
||||||
|
@ -68,6 +70,7 @@ impl RepoInfo {
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|x| !x.cannot_be_a_base())
|
.filter(|x| !x.cannot_be_a_base())
|
||||||
.or_else(|| Url::parse(&format!("https://{host}/")).ok())
|
.or_else(|| Url::parse(&format!("https://{host}/")).ok())
|
||||||
|
.map(|url| keys.deref_alias(url))
|
||||||
});
|
});
|
||||||
|
|
||||||
let (remote_url, remote_repo_name) = {
|
let (remote_url, remote_repo_name) = {
|
||||||
|
@ -97,7 +100,7 @@ impl RepoInfo {
|
||||||
if let Some(host_url) = &host_url {
|
if let Some(host_url) = &host_url {
|
||||||
let remote = local_repo.find_remote(remote_name_s)?;
|
let remote = local_repo.find_remote(remote_name_s)?;
|
||||||
let url_s = std::str::from_utf8(remote.url_bytes())?;
|
let url_s = std::str::from_utf8(remote.url_bytes())?;
|
||||||
let url = crate::ssh_url_parse(url_s)?;
|
let url = keys.deref_alias(crate::ssh_url_parse(url_s)?);
|
||||||
|
|
||||||
if crate::host_with_port(&url) == crate::host_with_port(host_url) {
|
if crate::host_with_port(&url) == crate::host_with_port(host_url) {
|
||||||
name = Some(remote_name_s.to_owned());
|
name = Some(remote_name_s.to_owned());
|
||||||
|
@ -123,8 +126,9 @@ impl RepoInfo {
|
||||||
let remote = local_repo.find_remote(remote_name)?;
|
let remote = local_repo.find_remote(remote_name)?;
|
||||||
|
|
||||||
if let Some(url) = remote.url() {
|
if let Some(url) = remote.url() {
|
||||||
let (url, _) = url_strip_repo_name(crate::ssh_url_parse(url)?)?;
|
let url = keys.deref_alias(crate::ssh_url_parse(url)?);
|
||||||
if crate::host_with_port(&url) == crate::host_with_port(host_url)
|
let (url, _) = url_strip_repo_name(url)?;
|
||||||
|
if crate::host_with_port(&url) == crate::host_with_port(&url)
|
||||||
&& url.path() == host_url.path()
|
&& url.path() == host_url.path()
|
||||||
{
|
{
|
||||||
name = Some(remote_name.to_owned());
|
name = Some(remote_name.to_owned());
|
||||||
|
@ -138,7 +142,7 @@ impl RepoInfo {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
if let Ok(remote) = local_repo.find_remote(&name) {
|
if let Ok(remote) = local_repo.find_remote(&name) {
|
||||||
let url_s = std::str::from_utf8(remote.url_bytes())?;
|
let url_s = std::str::from_utf8(remote.url_bytes())?;
|
||||||
let url = crate::ssh_url_parse(url_s)?;
|
let url = keys.deref_alias(crate::ssh_url_parse(url_s)?);
|
||||||
let (url, name) = url_strip_repo_name(url)?;
|
let (url, name) = url_strip_repo_name(url)?;
|
||||||
|
|
||||||
out = (Some(url), Some(name))
|
out = (Some(url), Some(name))
|
||||||
|
@ -362,7 +366,7 @@ impl RepoCommand {
|
||||||
remote,
|
remote,
|
||||||
push,
|
push,
|
||||||
} => {
|
} => {
|
||||||
let host = RepoInfo::get_current(host_name, None, None)?;
|
let host = RepoInfo::get_current(host_name, None, None, &keys)?;
|
||||||
let api = keys.get_api(host.host_url()).await?;
|
let api = keys.get_api(host.host_url()).await?;
|
||||||
create_repo(&api, repo, description, private, remote, push).await?;
|
create_repo(&api, repo, description, private, remote, push).await?;
|
||||||
}
|
}
|
||||||
|
@ -381,7 +385,8 @@ impl RepoCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let repo_info = RepoInfo::get_current(host_name, Some(&repo), remote.as_deref())?;
|
let repo_info =
|
||||||
|
RepoInfo::get_current(host_name, Some(&repo), remote.as_deref(), &keys)?;
|
||||||
let api = keys.get_api(repo_info.host_url()).await?;
|
let api = keys.get_api(repo_info.host_url()).await?;
|
||||||
let repo = repo_info
|
let repo = repo_info
|
||||||
.name()
|
.name()
|
||||||
|
@ -389,7 +394,8 @@ impl RepoCommand {
|
||||||
fork_repo(&api, repo, name).await?
|
fork_repo(&api, repo, name).await?
|
||||||
}
|
}
|
||||||
RepoCommand::View { name, remote } => {
|
RepoCommand::View { name, remote } => {
|
||||||
let repo = RepoInfo::get_current(host_name, name.as_ref(), remote.as_deref())?;
|
let repo =
|
||||||
|
RepoInfo::get_current(host_name, name.as_ref(), remote.as_deref(), &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let repo = repo
|
let repo = repo
|
||||||
.name()
|
.name()
|
||||||
|
@ -397,20 +403,20 @@ impl RepoCommand {
|
||||||
view_repo(&api, repo).await?
|
view_repo(&api, repo).await?
|
||||||
}
|
}
|
||||||
RepoCommand::Clone { repo, path } => {
|
RepoCommand::Clone { repo, path } => {
|
||||||
let repo = RepoInfo::get_current(host_name, Some(&repo), None)?;
|
let repo = RepoInfo::get_current(host_name, Some(&repo), None, &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let name = repo.name().unwrap();
|
let name = repo.name().unwrap();
|
||||||
cmd_clone_repo(&api, name, path).await?;
|
cmd_clone_repo(&api, name, path).await?;
|
||||||
}
|
}
|
||||||
RepoCommand::Star { repo } => {
|
RepoCommand::Star { repo } => {
|
||||||
let repo = RepoInfo::get_current(host_name, Some(&repo), None)?;
|
let repo = RepoInfo::get_current(host_name, Some(&repo), None, &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let name = repo.name().unwrap();
|
let name = repo.name().unwrap();
|
||||||
api.user_current_put_star(name.owner(), name.name()).await?;
|
api.user_current_put_star(name.owner(), name.name()).await?;
|
||||||
println!("Starred {}/{}!", name.owner(), name.name());
|
println!("Starred {}/{}!", name.owner(), name.name());
|
||||||
}
|
}
|
||||||
RepoCommand::Unstar { repo } => {
|
RepoCommand::Unstar { repo } => {
|
||||||
let repo = RepoInfo::get_current(host_name, Some(&repo), None)?;
|
let repo = RepoInfo::get_current(host_name, Some(&repo), None, &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let name = repo.name().unwrap();
|
let name = repo.name().unwrap();
|
||||||
api.user_current_delete_star(name.owner(), name.name())
|
api.user_current_delete_star(name.owner(), name.name())
|
||||||
|
@ -418,13 +424,14 @@ impl RepoCommand {
|
||||||
println!("Removed star from {}/{}", name.owner(), name.name());
|
println!("Removed star from {}/{}", name.owner(), name.name());
|
||||||
}
|
}
|
||||||
RepoCommand::Delete { repo } => {
|
RepoCommand::Delete { repo } => {
|
||||||
let repo = RepoInfo::get_current(host_name, Some(&repo), None)?;
|
let repo = RepoInfo::get_current(host_name, Some(&repo), None, &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let name = repo.name().unwrap();
|
let name = repo.name().unwrap();
|
||||||
delete_repo(&api, name).await?;
|
delete_repo(&api, name).await?;
|
||||||
}
|
}
|
||||||
RepoCommand::Browse { name, remote } => {
|
RepoCommand::Browse { name, remote } => {
|
||||||
let repo = RepoInfo::get_current(host_name, name.as_ref(), remote.as_deref())?;
|
let repo =
|
||||||
|
RepoInfo::get_current(host_name, name.as_ref(), remote.as_deref(), &keys)?;
|
||||||
let mut url = repo.host_url().clone();
|
let mut url = repo.host_url().clone();
|
||||||
let repo = repo
|
let repo = repo
|
||||||
.name()
|
.name()
|
||||||
|
|
|
@ -173,7 +173,7 @@ pub enum VisbilitySetting {
|
||||||
|
|
||||||
impl UserCommand {
|
impl UserCommand {
|
||||||
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
||||||
let repo = RepoInfo::get_current(host_name, None, self.remote.as_deref())?;
|
let repo = RepoInfo::get_current(host_name, None, self.remote.as_deref(), &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
match self.command {
|
match self.command {
|
||||||
UserSubcommand::Search { query, page } => user_search(&api, &query, page).await?,
|
UserSubcommand::Search { query, page } => user_search(&api, &query, page).await?,
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl WikiCommand {
|
||||||
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
|
||||||
use WikiSubcommand::*;
|
use WikiSubcommand::*;
|
||||||
|
|
||||||
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref())?;
|
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref(), &keys)?;
|
||||||
let api = keys.get_api(repo.host_url()).await?;
|
let api = keys.get_api(repo.host_url()).await?;
|
||||||
let repo = repo
|
let repo = repo
|
||||||
.name()
|
.name()
|
||||||
|
|
Loading…
Reference in a new issue