mirror of
https://codeberg.org/Cyborus/forgejo-cli.git
synced 2025-04-25 08:13:03 +02:00
Replace calls to git with git2
crate (#5)
This commit is contained in:
parent
2c30803476
commit
c1806f5aa6
4 changed files with 180 additions and 98 deletions
68
Cargo.lock
generated
68
Cargo.lock
generated
|
@ -122,6 +122,9 @@ name = "cc"
|
||||||
version = "1.0.79"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
|
dependencies = [
|
||||||
|
"jobserver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
@ -271,6 +274,7 @@ dependencies = [
|
||||||
"eyre",
|
"eyre",
|
||||||
"forgejo-api",
|
"forgejo-api",
|
||||||
"futures",
|
"futures",
|
||||||
|
"git2",
|
||||||
"open",
|
"open",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -428,6 +432,21 @@ version = "0.27.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
|
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "git2"
|
||||||
|
version = "0.17.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
"libgit2-sys",
|
||||||
|
"log",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.20"
|
version = "0.3.20"
|
||||||
|
@ -624,6 +643,15 @@ version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.64"
|
version = "0.3.64"
|
||||||
|
@ -645,6 +673,46 @@ version = "0.2.147"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libgit2-sys"
|
||||||
|
version = "0.15.2+1.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libssh2-sys",
|
||||||
|
"libz-sys",
|
||||||
|
"openssl-sys",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libssh2-sys"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libz-sys",
|
||||||
|
"openssl-sys",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libz-sys"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
|
|
|
@ -11,6 +11,7 @@ directories = "5.0.1"
|
||||||
eyre = "0.6.8"
|
eyre = "0.6.8"
|
||||||
forgejo-api = { path = "./forgejo-api" }
|
forgejo-api = { path = "./forgejo-api" }
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
|
git2 = "0.17.2"
|
||||||
open = "5.0.0"
|
open = "5.0.0"
|
||||||
serde = { version = "1.0.170", features = ["derive"] }
|
serde = { version = "1.0.170", features = ["derive"] }
|
||||||
serde_json = "1.0.100"
|
serde_json = "1.0.100"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
use reqwest::{Client, Request, StatusCode};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use url::Url;
|
|
||||||
use soft_assert::*;
|
use soft_assert::*;
|
||||||
use reqwest::{Client, StatusCode, Request};
|
use url::Url;
|
||||||
|
|
||||||
pub struct Forgejo {
|
pub struct Forgejo {
|
||||||
url: Url,
|
url: Url,
|
||||||
|
@ -23,7 +23,7 @@ pub enum ForgejoError {
|
||||||
#[error("unexpected status code {} {}", .0.as_u16(), .0.canonical_reason().unwrap_or(""))]
|
#[error("unexpected status code {} {}", .0.as_u16(), .0.canonical_reason().unwrap_or(""))]
|
||||||
UnexpectedStatusCode(StatusCode),
|
UnexpectedStatusCode(StatusCode),
|
||||||
#[error("{} {}: {}", .0.as_u16(), .0.canonical_reason().unwrap_or(""), .1)]
|
#[error("{} {}: {}", .0.as_u16(), .0.canonical_reason().unwrap_or(""), .1)]
|
||||||
ApiError(StatusCode, String)
|
ApiError(StatusCode, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<reqwest::Error> for ForgejoError {
|
impl From<reqwest::Error> for ForgejoError {
|
||||||
|
@ -37,23 +37,32 @@ impl From<reqwest::Error> for ForgejoError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Forgejo {
|
impl Forgejo {
|
||||||
pub fn new(api_key: &str, url: Url) -> Result<Self, ForgejoError> {
|
pub fn new(api_key: &str, url: Url) -> Result<Self, ForgejoError> {
|
||||||
Self::with_user_agent(api_key, url, "forgejo-api-rs")
|
Self::with_user_agent(api_key, url, "forgejo-api-rs")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_user_agent(api_key: &str, url: Url, user_agent: &str) -> Result<Self, ForgejoError> {
|
pub fn with_user_agent(
|
||||||
soft_assert!(matches!(url.scheme(), "http" | "https"), Err(ForgejoError::HttpRequired));
|
api_key: &str,
|
||||||
|
url: Url,
|
||||||
|
user_agent: &str,
|
||||||
|
) -> Result<Self, ForgejoError> {
|
||||||
|
soft_assert!(
|
||||||
|
matches!(url.scheme(), "http" | "https"),
|
||||||
|
Err(ForgejoError::HttpRequired)
|
||||||
|
);
|
||||||
|
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
let mut key_header: reqwest::header::HeaderValue = format!("token {api_key}").try_into().map_err(|_| ForgejoError::KeyNotAscii)?;
|
let mut key_header: reqwest::header::HeaderValue = format!("token {api_key}")
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| ForgejoError::KeyNotAscii)?;
|
||||||
// key_header.set_sensitive(true);
|
// key_header.set_sensitive(true);
|
||||||
headers.insert("Authorization", key_header);
|
headers.insert("Authorization", key_header);
|
||||||
let client = Client::builder().user_agent(user_agent).default_headers(headers).build()?;
|
let client = Client::builder()
|
||||||
|
.user_agent(user_agent)
|
||||||
|
.default_headers(headers)
|
||||||
|
.build()?;
|
||||||
dbg!(&client);
|
dbg!(&client);
|
||||||
Ok(Self {
|
Ok(Self { url, client })
|
||||||
url,
|
|
||||||
client,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_repo(&self, user: &str, repo: &str) -> Result<Option<Repo>, ForgejoError> {
|
pub async fn get_repo(&self, user: &str, repo: &str) -> Result<Option<Repo>, ForgejoError> {
|
||||||
|
@ -93,29 +102,42 @@ impl Forgejo {
|
||||||
self.execute_opt(request).await
|
self.execute_opt(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post<T: Serialize, U: DeserializeOwned>(&self, path: &str, body: &T) -> Result<U, ForgejoError> {
|
async fn post<T: Serialize, U: DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
body: &T,
|
||||||
|
) -> Result<U, ForgejoError> {
|
||||||
let url = self.url.join("api/v1/").unwrap().join(path).unwrap();
|
let url = self.url.join("api/v1/").unwrap().join(path).unwrap();
|
||||||
let request = self.client.post(url).json(body).build()?;
|
let request = self.client.post(url).json(body).build()?;
|
||||||
self.execute(request).await
|
self.execute(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute<T: DeserializeOwned>(&self, request: Request) -> Result<T, ForgejoError> {
|
async fn execute<T: DeserializeOwned>(&self, request: Request) -> Result<T, ForgejoError> {
|
||||||
let response = self.client.execute(dbg!(request)).await?;
|
let response = self.client.execute(dbg!(request)).await?;
|
||||||
match response.status() {
|
match response.status() {
|
||||||
status if status.is_success() => Ok(response.json::<T>().await?),
|
status if status.is_success() => Ok(response.json::<T>().await?),
|
||||||
status if status.is_client_error() => Err(ForgejoError::ApiError(status, response.json::<ErrorMessage>().await?.message)),
|
status if status.is_client_error() => Err(ForgejoError::ApiError(
|
||||||
status => Err(ForgejoError::UnexpectedStatusCode(status))
|
status,
|
||||||
|
response.json::<ErrorMessage>().await?.message,
|
||||||
|
)),
|
||||||
|
status => Err(ForgejoError::UnexpectedStatusCode(status)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `execute`, but returns `Ok(None)` on 404.
|
/// Like `execute`, but returns `Ok(None)` on 404.
|
||||||
async fn execute_opt<T: DeserializeOwned>(&self, request: Request) -> Result<Option<T>, ForgejoError> {
|
async fn execute_opt<T: DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
request: Request,
|
||||||
|
) -> Result<Option<T>, ForgejoError> {
|
||||||
let response = self.client.execute(dbg!(request)).await?;
|
let response = self.client.execute(dbg!(request)).await?;
|
||||||
match response.status() {
|
match response.status() {
|
||||||
status if status.is_success() => Ok(Some(response.json::<T>().await?)),
|
status if status.is_success() => Ok(Some(response.json::<T>().await?)),
|
||||||
StatusCode::NOT_FOUND => Ok(None),
|
StatusCode::NOT_FOUND => Ok(None),
|
||||||
status if status.is_client_error() => Err(ForgejoError::ApiError(status, response.json::<ErrorMessage>().await?.message)),
|
status if status.is_client_error() => Err(ForgejoError::ApiError(
|
||||||
status => Err(ForgejoError::UnexpectedStatusCode(status))
|
status,
|
||||||
|
response.json::<ErrorMessage>().await?.message,
|
||||||
|
)),
|
||||||
|
status => Err(ForgejoError::UnexpectedStatusCode(status)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,14 +146,13 @@ impl Forgejo {
|
||||||
struct ErrorMessage {
|
struct ErrorMessage {
|
||||||
message: String,
|
message: String,
|
||||||
// intentionally ignored, no need for now
|
// intentionally ignored, no need for now
|
||||||
// url: Url
|
// url: Url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug, PartialEq)]
|
#[derive(serde::Deserialize, Debug, PartialEq)]
|
||||||
pub struct Repo {
|
pub struct Repo {
|
||||||
pub clone_url: Url,
|
pub clone_url: Url,
|
||||||
#[serde(with="time::serde::rfc3339")]
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
pub created_at: time::OffsetDateTime,
|
pub created_at: time::OffsetDateTime,
|
||||||
pub default_branch: String,
|
pub default_branch: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
@ -146,7 +167,7 @@ pub struct Repo {
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub avatar_url: Url,
|
pub avatar_url: Url,
|
||||||
#[serde(with="time::serde::rfc3339")]
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
pub created: time::OffsetDateTime,
|
pub created: time::OffsetDateTime,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
@ -156,7 +177,7 @@ pub struct User {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
pub language: String,
|
pub language: String,
|
||||||
#[serde(with="time::serde::rfc3339")]
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
pub last_login: time::OffsetDateTime,
|
pub last_login: time::OffsetDateTime,
|
||||||
pub location: String,
|
pub location: String,
|
||||||
pub login: String,
|
pub login: String,
|
||||||
|
@ -189,7 +210,7 @@ pub struct CreateRepoOption {
|
||||||
pub private: bool,
|
pub private: bool,
|
||||||
pub readme: String,
|
pub readme: String,
|
||||||
pub template: bool,
|
pub template: bool,
|
||||||
pub trust_model: TrustModel
|
pub trust_model: TrustModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, Debug, PartialEq)]
|
#[derive(serde::Serialize, Debug, PartialEq)]
|
||||||
|
@ -199,4 +220,4 @@ pub enum TrustModel {
|
||||||
Committer,
|
Committer,
|
||||||
#[serde(rename = "collaboratorcommiter")]
|
#[serde(rename = "collaboratorcommiter")]
|
||||||
CollaboratorCommitter,
|
CollaboratorCommitter,
|
||||||
}
|
}
|
||||||
|
|
136
src/main.rs
136
src/main.rs
|
@ -26,8 +26,8 @@ pub enum Command {
|
||||||
|
|
||||||
#[derive(Subcommand, Clone, Debug)]
|
#[derive(Subcommand, Clone, Debug)]
|
||||||
pub enum RepoCommand {
|
pub enum RepoCommand {
|
||||||
Create {
|
Create {
|
||||||
host: String,
|
host: String,
|
||||||
repo: String,
|
repo: String,
|
||||||
|
|
||||||
// flags
|
// flags
|
||||||
|
@ -41,7 +41,7 @@ pub enum RepoCommand {
|
||||||
/// Pushes the current branch to the default branch on the new repo.
|
/// Pushes the current branch to the default branch on the new repo.
|
||||||
/// Implies `--set-upstream=origin` (setting upstream manual overrides this)
|
/// Implies `--set-upstream=origin` (setting upstream manual overrides this)
|
||||||
#[clap(long, short)]
|
#[clap(long, short)]
|
||||||
push: bool
|
push: bool,
|
||||||
},
|
},
|
||||||
Info,
|
Info,
|
||||||
Browse,
|
Browse,
|
||||||
|
@ -81,9 +81,9 @@ async fn main() -> eyre::Result<()> {
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Command::Repo(repo_subcommand) => match repo_subcommand {
|
Command::Repo(repo_subcommand) => match repo_subcommand {
|
||||||
RepoCommand::Create {
|
RepoCommand::Create {
|
||||||
host,
|
host,
|
||||||
repo ,
|
repo,
|
||||||
|
|
||||||
description,
|
description,
|
||||||
private,
|
private,
|
||||||
|
@ -91,10 +91,12 @@ async fn main() -> eyre::Result<()> {
|
||||||
push,
|
push,
|
||||||
} => {
|
} => {
|
||||||
// let (host_domain, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
// 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 host_info = keys
|
||||||
|
.hosts
|
||||||
|
.get(&host)
|
||||||
|
.ok_or_else(|| eyre!("not a known host"))?;
|
||||||
let (_, user) = host_info.get_current_user()?;
|
let (_, user) = host_info.get_current_user()?;
|
||||||
let url = Url::parse(&format!("http://{host}/"))?;
|
let api = Forgejo::new(&user.key, host_info.url.clone())?;
|
||||||
let api = Forgejo::new(&user.key, url.clone())?;
|
|
||||||
let repo_spec = CreateRepoOption {
|
let repo_spec = CreateRepoOption {
|
||||||
auto_init: false,
|
auto_init: false,
|
||||||
default_branch: "main".into(),
|
default_branch: "main".into(),
|
||||||
|
@ -109,41 +111,28 @@ async fn main() -> eyre::Result<()> {
|
||||||
trust_model: forgejo_api::TrustModel::Default,
|
trust_model: forgejo_api::TrustModel::Default,
|
||||||
};
|
};
|
||||||
let new_repo = api.create_repo(repo_spec).await?;
|
let new_repo = api.create_repo(repo_spec).await?;
|
||||||
eprintln!("created new repo at {}", url.join(&format!("{}/{}", user.name, repo))?);
|
eprintln!(
|
||||||
|
"created new repo at {}",
|
||||||
|
host_info.url.join(&format!("{}/{}", user.name, repo))?
|
||||||
|
);
|
||||||
|
|
||||||
let upstream = set_upstream.as_deref().unwrap_or("origin");
|
let upstream = set_upstream.as_deref().unwrap_or("origin");
|
||||||
|
|
||||||
if set_upstream.is_some() || push {
|
let repo = git2::Repository::open(".")?;
|
||||||
let status = tokio::process::Command::new("git")
|
let remote = if set_upstream.is_some() || push {
|
||||||
.arg("remote")
|
repo.remote(upstream, new_repo.clone_url.as_str())?;
|
||||||
.arg("add")
|
} else {
|
||||||
.arg(upstream)
|
repo.find_remote(upstream)?;
|
||||||
.arg(new_repo.clone_url.as_str())
|
};
|
||||||
.status()
|
|
||||||
.await?;
|
|
||||||
if !status.success() {
|
|
||||||
eprintln!("origin set failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if push {
|
if push {
|
||||||
let status = tokio::process::Command::new("git")
|
remote.push(upstream)?;
|
||||||
.arg("push")
|
|
||||||
.arg("-u")
|
|
||||||
.arg(upstream)
|
|
||||||
.arg("main")
|
|
||||||
.status()
|
|
||||||
.await?;
|
|
||||||
if !status.success() {
|
|
||||||
eprintln!("push failed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RepoCommand::Info => {
|
RepoCommand::Info => {
|
||||||
let (host_domain, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
let (_, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
||||||
let (_, user) = host_keys.get_current_user()?;
|
let (_, user) = host_keys.get_current_user()?;
|
||||||
let url = Url::parse(&format!("http://{host_domain}/"))?;
|
let api = Forgejo::new(&user.key, host_keys.url.clone())?;
|
||||||
let api = Forgejo::new(&user.key, url)?;
|
|
||||||
let repo = api.get_repo(&user.name, &repo).await?;
|
let repo = api.get_repo(&user.name, &repo).await?;
|
||||||
match repo {
|
match repo {
|
||||||
Some(repo) => {
|
Some(repo) => {
|
||||||
|
@ -153,19 +142,27 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RepoCommand::Browse => {
|
RepoCommand::Browse => {
|
||||||
let (host_domain, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
let (_, host_keys, repo) = keys.get_current_host_and_repo().await?;
|
||||||
let (_, user) = host_keys.get_current_user()?;
|
let (_, user) = host_keys.get_current_user()?;
|
||||||
open::that(format!("http://{host_domain}/{}/{repo}", user.name))?;
|
open::that(
|
||||||
|
host_keys
|
||||||
|
.url
|
||||||
|
.join(&format!("/{}/{repo}", user.name))?
|
||||||
|
.as_str(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Command::User { host } => {
|
Command::User { host } => {
|
||||||
let (host_domain, host_keys) = match host.as_deref() {
|
let (_, host_keys) = match host.as_deref() {
|
||||||
Some(s) => (s, keys.hosts.get(s).ok_or_else(|| eyre!("not a known host"))?),
|
Some(s) => (
|
||||||
|
s,
|
||||||
|
keys.hosts.get(s).ok_or_else(|| eyre!("not a known host"))?,
|
||||||
|
),
|
||||||
None => keys.get_current_host().await?,
|
None => keys.get_current_host().await?,
|
||||||
};
|
};
|
||||||
let (_, info) = host_keys.get_current_user()?;
|
let (_, info) = host_keys.get_current_user()?;
|
||||||
eprintln!("currently signed in to {}@{}", info.name, host_domain);
|
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 => {
|
||||||
todo!();
|
todo!();
|
||||||
|
@ -206,7 +203,10 @@ async fn main() -> eyre::Result<()> {
|
||||||
name,
|
name,
|
||||||
key,
|
key,
|
||||||
} => {
|
} => {
|
||||||
let host_keys = keys.hosts.entry(host.clone()).or_default();
|
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?,
|
||||||
|
@ -254,30 +254,16 @@ async fn readline(msg: &str) -> eyre::Result<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_remotes() -> eyre::Result<Vec<(String, Url)>> {
|
async fn get_remotes() -> eyre::Result<Vec<(String, Url)>> {
|
||||||
let remotes = String::from_utf8(
|
let repo = git2::Repository::open(".")?;
|
||||||
tokio::process::Command::new("git")
|
let remotes = repo
|
||||||
.arg("remote")
|
.remotes()?
|
||||||
.output()
|
.iter()
|
||||||
.await?
|
.filter_map(|name| {
|
||||||
.stdout,
|
let name = name?.to_string();
|
||||||
)?;
|
let url = Url::parse(repo.find_remote(&name).ok()?.url()?).ok()?;
|
||||||
let remotes = futures::future::try_join_all(remotes.lines().map(|name| async {
|
Some((name, url))
|
||||||
let name = name.trim();
|
})
|
||||||
let url = Url::parse(
|
.collect::<Vec<_>>();
|
||||||
String::from_utf8(
|
|
||||||
tokio::process::Command::new("git")
|
|
||||||
.arg("remote")
|
|
||||||
.arg("get-url")
|
|
||||||
.arg(name)
|
|
||||||
.output()
|
|
||||||
.await?
|
|
||||||
.stdout,
|
|
||||||
)?
|
|
||||||
.trim(),
|
|
||||||
)?;
|
|
||||||
Ok::<_, eyre::Report>((name.to_string(), url))
|
|
||||||
}))
|
|
||||||
.await?;
|
|
||||||
Ok(remotes)
|
Ok(remotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,9 +281,14 @@ async fn get_remote(remotes: &[(String, Url)]) -> eyre::Result<Url> {
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
|
||||||
struct KeyInfo {
|
struct KeyInfo {
|
||||||
hosts: BTreeMap<String, HostInfo>,
|
hosts: BTreeMap<String, HostInfo>,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
async fn load() -> eyre::Result<Self> {
|
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"))?
|
||||||
|
@ -342,10 +333,13 @@ impl KeyInfo {
|
||||||
} 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 (name, host) = self
|
||||||
.hosts
|
.hosts
|
||||||
.get_key_value(&domain)
|
.get_key_value(name)
|
||||||
.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((name, host, repo_from_url(&remote)?.into()))
|
||||||
}
|
}
|
||||||
|
@ -381,9 +375,10 @@ fn repo_from_url(url: &Url) -> eyre::Result<&str> {
|
||||||
Ok(repo)
|
Ok(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
struct HostInfo {
|
struct HostInfo {
|
||||||
default: Option<String>,
|
default: Option<String>,
|
||||||
|
url: Url,
|
||||||
users: BTreeMap<String, UserInfo>,
|
users: BTreeMap<String, UserInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,10 +388,7 @@ impl HostInfo {
|
||||||
let (s, k) = self.users.first_key_value().unwrap();
|
let (s, k) = self.users.first_key_value().unwrap();
|
||||||
return Ok((s, k));
|
return Ok((s, k));
|
||||||
}
|
}
|
||||||
if let Some(default) = self
|
if let Some(default) = self.default.as_ref() {
|
||||||
.default
|
|
||||||
.as_ref()
|
|
||||||
{
|
|
||||||
if let Some(default_info) = self.users.get(default) {
|
if let Some(default_info) = self.users.get(default) {
|
||||||
return Ok((default, default_info));
|
return Ok((default, default_info));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue