feat: use local package instead of octocrab
Some checks failed
Release / release (push) Failing after 2m54s

This commit is contained in:
2026-03-20 17:05:25 +01:00
parent 746ed6c9e3
commit 5813c5ca21
3 changed files with 127 additions and 332 deletions

View File

@@ -24,9 +24,10 @@ uuid = { version = "1.7.0", features = ["v4"] }
async-trait = "0.1.82"
toml = "0.8.19"
gitea-client = { version = "1.25.2", default-features = false, features = ["rustls"] }
gitea-client = { git = "https://git.kjuulh.io/kjuulh/gitea-rs.git", default-features = false, features = ["rustls-ring"] }
reqwest = { version = "0.13", default-features = false, features = ["__rustls", "json", "webpki-roots"] }
rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
url = "2.5.2"
octocrab = "0.43.0"
dirs = "6.0.0"
prost = "0.13.2"
prost-types = "0.13.2"

View File

@@ -1,7 +1,20 @@
use octocrab::{models::Repository, params::repos::Sort, Octocrab, Page};
use reqwest::header::{ACCEPT, AUTHORIZATION, USER_AGENT};
use serde::Deserialize;
use crate::{app::App, config::GitHubAccessToken};
#[derive(Deserialize)]
struct GitHubRepo {
name: String,
owner: Option<GitHubOwner>,
ssh_url: Option<String>,
}
#[derive(Deserialize)]
struct GitHubOwner {
login: String,
}
pub struct GitHubProvider {
#[allow(dead_code)]
app: &'static App,
@@ -19,30 +32,17 @@ impl GitHubProvider {
) -> anyhow::Result<Vec<super::Repository>> {
tracing::debug!("fetching github repositories for current user");
let client = self.get_client(url, access_token)?;
let client = self.get_client(access_token)?;
let base = self.api_base(url);
let current_page = client
.current()
.list_repos_for_authenticated_user()
.type_("all")
.per_page(100)
.sort("full_name")
.send()
let repos: Vec<GitHubRepo> = self
.paginate(
&client,
&format!("{base}/user/repos?type=all&sort=full_name&per_page=100"),
)
.await?;
let repos = self.unfold_pages(client, current_page).await?;
Ok(repos
.into_iter()
.filter_map(|repo| {
Some(super::Repository {
provider: self.get_url(url),
owner: repo.owner.map(|su| su.login)?,
repo_name: repo.name,
ssh_url: repo.ssh_url?,
})
})
.collect())
Ok(self.to_repositories(url, repos))
}
pub async fn list_repositories_for_user(
@@ -53,30 +53,17 @@ impl GitHubProvider {
) -> anyhow::Result<Vec<super::Repository>> {
tracing::debug!(user = user, "fetching github repositories for user");
let client = self.get_client(url, access_token)?;
let client = self.get_client(access_token)?;
let base = self.api_base(url);
let current_page = client
.users(user)
.repos()
.r#type(octocrab::params::users::repos::Type::All)
.sort(Sort::FullName)
.per_page(100)
.send()
let repos: Vec<GitHubRepo> = self
.paginate(
&client,
&format!("{base}/users/{user}/repos?type=all&sort=full_name&per_page=100"),
)
.await?;
let repos = self.unfold_pages(client, current_page).await?;
Ok(repos
.into_iter()
.filter_map(|repo| {
Some(super::Repository {
provider: self.get_url(url),
owner: repo.owner.map(|su| su.login)?,
repo_name: repo.name,
ssh_url: repo.ssh_url?,
})
})
.collect())
Ok(self.to_repositories(url, repos))
}
pub async fn list_repositories_for_organisation(
@@ -90,47 +77,66 @@ impl GitHubProvider {
"fetching github repositories for organisation"
);
let client = self.get_client(url, access_token)?;
let client = self.get_client(access_token)?;
let base = self.api_base(url);
let current_page = client
.orgs(organisation)
.list_repos()
.repo_type(Some(octocrab::params::repos::Type::All))
.sort(Sort::FullName)
.per_page(100)
.send()
let repos: Vec<GitHubRepo> = self
.paginate(
&client,
&format!("{base}/orgs/{organisation}/repos?type=all&sort=full_name&per_page=100"),
)
.await?;
let repos = self.unfold_pages(client, current_page).await?;
Ok(self.to_repositories(url, repos))
}
Ok(repos
async fn paginate(
&self,
client: &reqwest::Client,
initial_url: &str,
) -> anyhow::Result<Vec<GitHubRepo>> {
let mut repos = Vec::new();
let mut url = Some(initial_url.to_string());
while let Some(current_url) = url {
let resp = client
.get(&current_url)
.send()
.await?
.error_for_status()?;
url = parse_next_link(resp.headers());
let page: Vec<GitHubRepo> = resp.json().await?;
repos.extend(page);
}
Ok(repos)
}
fn to_repositories(
&self,
url: Option<&String>,
repos: Vec<GitHubRepo>,
) -> Vec<super::Repository> {
repos
.into_iter()
.filter_map(|repo| {
Some(super::Repository {
provider: self.get_url(url),
owner: repo.owner.map(|su| su.login)?,
owner: repo.owner.map(|o| o.login)?,
repo_name: repo.name,
ssh_url: repo.ssh_url?,
})
})
.collect())
.collect()
}
async fn unfold_pages(
&self,
client: octocrab::Octocrab,
page: Page<Repository>,
) -> anyhow::Result<Vec<Repository>> {
let mut current_page = page;
let mut repos = current_page.take_items();
while let Ok(Some(mut new_page)) = client.get_page(&current_page.next).await {
repos.extend(new_page.take_items());
current_page = new_page;
fn api_base(&self, url: Option<&String>) -> String {
match url {
Some(u) => format!("{u}/api/v3"),
None => "https://api.github.com".to_string(),
}
Ok(repos)
}
fn get_url(&self, url: Option<&String>) -> String {
@@ -151,15 +157,19 @@ impl GitHubProvider {
}
}
fn get_client(
&self,
_url: Option<&String>,
access_token: &GitHubAccessToken,
) -> anyhow::Result<Octocrab> {
let client = octocrab::Octocrab::builder()
.personal_token(match access_token {
GitHubAccessToken::Direct(token) => token.to_owned(),
GitHubAccessToken::Env { env } => std::env::var(env)?,
fn get_client(&self, access_token: &GitHubAccessToken) -> anyhow::Result<reqwest::Client> {
let token = match access_token {
GitHubAccessToken::Direct(token) => token.to_owned(),
GitHubAccessToken::Env { env } => std::env::var(env)?,
};
let client = reqwest::Client::builder()
.default_headers({
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(AUTHORIZATION, format!("token {token}").parse()?);
headers.insert(ACCEPT, "application/vnd.github+json".parse()?);
headers.insert(USER_AGENT, "gitnow".parse()?);
headers
})
.build()?;
@@ -167,6 +177,18 @@ impl GitHubProvider {
}
}
fn parse_next_link(headers: &reqwest::header::HeaderMap) -> Option<String> {
let link = headers.get("link")?.to_str().ok()?;
for part in link.split(',') {
let part = part.trim();
if part.ends_with("rel=\"next\"") {
let url = part.split('>').next()?.trim_start_matches('<');
return Some(url.to_string());
}
}
None
}
pub trait GitHubProviderApp {
fn github_provider(&self) -> GitHubProvider;
}