diff --git a/src/auth.rs b/src/auth.rs index 40dedd5..388e5ef 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -26,7 +26,7 @@ impl AuthCommand { pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> { match self { 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 client_info = get_client_info_for(host_url); if let Some((client_id, _)) = client_info { @@ -52,7 +52,7 @@ impl AuthCommand { } } 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 key = match key { Some(key) => key, @@ -60,13 +60,12 @@ impl AuthCommand { }; let host = crate::host_with_port(&host_url); if !keys.hosts.contains_key(host) { - keys.hosts.insert( - host.to_owned(), - crate::keys::LoginInfo::Application { - name: user, - token: key, - }, - ); + let mut login = crate::keys::LoginInfo::Application { + name: user, + token: key, + }; + add_ssh_alias(&mut login, host_url, keys).await; + keys.hosts.insert(host.to_owned(), login); } else { 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. 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 login_info = crate::keys::LoginInfo::OAuth { + let mut login_info = crate::keys::LoginInfo::OAuth { name, token: response.access_token, refresh_token: response.refresh_token, expires_at, }; + add_ssh_alias(&mut login_info, host, keys).await; let domain = crate::host_with_port(&host); keys.hosts.insert(domain.to_owned(), login_info); @@ -232,3 +232,38 @@ fn auth_server() -> ( }); (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 { + 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 +} diff --git a/src/issues.rs b/src/issues.rs index de8822a..091f221 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -168,7 +168,7 @@ pub enum ViewCommand { impl IssueCommand { pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> { 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 repo = repo.name().ok_or_else(|| self.no_repo_error())?; match self.command { diff --git a/src/keys.rs b/src/keys.rs index 58fc57d..e5fd6c5 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -6,6 +6,8 @@ use url::Url; #[derive(serde::Serialize, serde::Deserialize, Clone, Default)] pub struct KeyInfo { pub hosts: BTreeMap, + #[serde(default)] + pub aliases: BTreeMap, } impl KeyInfo { @@ -54,6 +56,21 @@ impl KeyInfo { pub async fn get_api(&mut self, url: &Url) -> eyre::Result { 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)] diff --git a/src/main.rs b/src/main.rs index 881bc40..5aaf2d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,7 +65,7 @@ async fn main() -> eyre::Result<()> { Command::Pr(subcommand) => subcommand.run(&mut keys, host_name).await?, Command::Wiki(subcommand) => subcommand.run(&mut keys, host_name).await?, 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")? .host_url() .clone(); diff --git a/src/prs.rs b/src/prs.rs index d66dcf5..637d084 100644 --- a/src/prs.rs +++ b/src/prs.rs @@ -264,7 +264,7 @@ pub enum ViewCommand { impl PrCommand { pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> { 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 repo = repo.name().ok_or_else(|| self.no_repo_error())?; match self.command { diff --git a/src/release.rs b/src/release.rs index 49dd5b5..6512014 100644 --- a/src/release.rs +++ b/src/release.rs @@ -126,7 +126,12 @@ pub enum AssetCommand { impl ReleaseCommand { 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 repo = repo .name() diff --git a/src/repo.rs b/src/repo.rs index 5221bf3..c55c51b 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -17,6 +17,7 @@ impl RepoInfo { host: Option<&str>, repo: Option<&RepoArg>, remote: Option<&str>, + keys: &crate::keys::KeyInfo, ) -> eyre::Result { // l = domain/owner/name // s = owner/name @@ -53,6 +54,7 @@ impl RepoInfo { .ok() .filter(|x| !x.cannot_be_a_base()) .or_else(|| Url::parse(&format!("https://{host}/")).ok()) + .map(|url| keys.deref_alias(url)) } repo_name = Some(RepoName { owner: repo.owner.clone(), @@ -68,6 +70,7 @@ impl RepoInfo { .ok() .filter(|x| !x.cannot_be_a_base()) .or_else(|| Url::parse(&format!("https://{host}/")).ok()) + .map(|url| keys.deref_alias(url)) }); let (remote_url, remote_repo_name) = { @@ -97,7 +100,7 @@ impl RepoInfo { if let Some(host_url) = &host_url { let remote = local_repo.find_remote(remote_name_s)?; 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) { name = Some(remote_name_s.to_owned()); @@ -123,8 +126,9 @@ impl RepoInfo { let remote = local_repo.find_remote(remote_name)?; if let Some(url) = remote.url() { - let (url, _) = url_strip_repo_name(crate::ssh_url_parse(url)?)?; - if crate::host_with_port(&url) == crate::host_with_port(host_url) + let url = keys.deref_alias(crate::ssh_url_parse(url)?); + let (url, _) = url_strip_repo_name(url)?; + if crate::host_with_port(&url) == crate::host_with_port(&url) && url.path() == host_url.path() { name = Some(remote_name.to_owned()); @@ -138,7 +142,7 @@ impl RepoInfo { if let Some(name) = name { if let Ok(remote) = local_repo.find_remote(&name) { 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)?; out = (Some(url), Some(name)) @@ -362,7 +366,7 @@ impl RepoCommand { remote, 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?; 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 repo = repo_info .name() @@ -389,7 +394,8 @@ impl RepoCommand { fork_repo(&api, repo, name).await? } 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 repo = repo .name() @@ -397,20 +403,20 @@ impl RepoCommand { view_repo(&api, repo).await? } 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 name = repo.name().unwrap(); cmd_clone_repo(&api, name, path).await?; } 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 name = repo.name().unwrap(); api.user_current_put_star(name.owner(), name.name()).await?; println!("Starred {}/{}!", name.owner(), name.name()); } 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 name = repo.name().unwrap(); api.user_current_delete_star(name.owner(), name.name()) @@ -418,13 +424,14 @@ impl RepoCommand { println!("Removed star from {}/{}", name.owner(), name.name()); } 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 name = repo.name().unwrap(); delete_repo(&api, name).await?; } 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 repo = repo .name() diff --git a/src/user.rs b/src/user.rs index 6f7de40..a40de28 100644 --- a/src/user.rs +++ b/src/user.rs @@ -173,7 +173,7 @@ pub enum VisbilitySetting { impl UserCommand { 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?; match self.command { UserSubcommand::Search { query, page } => user_search(&api, &query, page).await?, diff --git a/src/wiki.rs b/src/wiki.rs index 616789d..a98941d 100644 --- a/src/wiki.rs +++ b/src/wiki.rs @@ -45,7 +45,7 @@ impl WikiCommand { pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> { 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 repo = repo .name()