Detailed changes
@@ -3,7 +3,7 @@ use std::{ops::Range, sync::Arc};
use anyhow::Result;
use url::Url;
-use util::{github, http::HttpClient};
+use util::{codeberg, github, http::HttpClient};
use crate::Oid;
@@ -59,7 +59,7 @@ impl HostingProvider {
pub fn supports_avatars(&self) -> bool {
match self {
- HostingProvider::Github => true,
+ HostingProvider::Github | HostingProvider::Codeberg => true,
_ => false,
}
}
@@ -71,24 +71,27 @@ impl HostingProvider {
commit: Oid,
client: Arc<dyn HttpClient>,
) -> Result<Option<Url>> {
- match self {
+ Ok(match self {
HostingProvider::Github => {
let commit = commit.to_string();
-
- let author =
- github::fetch_github_commit_author(repo_owner, repo, &commit, &client).await?;
-
- let url = if let Some(author) = author {
- let mut url = Url::parse(&author.avatar_url)?;
- url.set_query(Some("size=128"));
- Some(url)
- } else {
- None
- };
- Ok(url)
+ github::fetch_github_commit_author(repo_owner, repo, &commit, &client)
+ .await?
+ .map(|author| -> Result<Url, url::ParseError> {
+ let mut url = Url::parse(&author.avatar_url)?;
+ url.set_query(Some("size=128"));
+ Ok(url)
+ })
+ .transpose()
+ }
+ HostingProvider::Codeberg => {
+ let commit = commit.to_string();
+ codeberg::fetch_codeberg_commit_author(repo_owner, repo, &commit, &client)
+ .await?
+ .map(|author| Url::parse(&author.avatar_url))
+ .transpose()
}
_ => Ok(None),
- }
+ }?)
}
}
@@ -0,0 +1,78 @@
+use crate::{git_author::GitAuthor, http::HttpClient};
+use anyhow::{bail, Context, Result};
+use futures::AsyncReadExt;
+use isahc::{config::Configurable, AsyncBody, Request};
+use serde::Deserialize;
+use std::sync::Arc;
+
+#[derive(Debug, Deserialize)]
+struct CommitDetails {
+ commit: Commit,
+ author: Option<User>,
+}
+
+#[derive(Debug, Deserialize)]
+struct Commit {
+ author: Author,
+}
+
+#[derive(Debug, Deserialize)]
+struct Author {
+ name: String,
+ email: String,
+ date: String,
+}
+
+#[derive(Debug, Deserialize)]
+struct User {
+ pub login: String,
+ pub id: u64,
+ pub avatar_url: String,
+}
+
+pub async fn fetch_codeberg_commit_author(
+ repo_owner: &str,
+ repo: &str,
+ commit: &str,
+ client: &Arc<dyn HttpClient>,
+) -> Result<Option<GitAuthor>> {
+ let url = format!("https://codeberg.org/api/v1/repos/{repo_owner}/{repo}/git/commits/{commit}");
+
+ let mut request = Request::get(&url)
+ .redirect_policy(isahc::config::RedirectPolicy::Follow)
+ .header("Content-Type", "application/json");
+
+ if let Ok(codeberg_token) = std::env::var("CODEBERG_TOKEN") {
+ request = request.header("Authorization", format!("Bearer {}", codeberg_token));
+ }
+
+ let mut response = client
+ .send(request.body(AsyncBody::default())?)
+ .await
+ .with_context(|| format!("error fetching Codeberg commit details at {:?}", url))?;
+
+ let mut body = Vec::new();
+ response.body_mut().read_to_end(&mut body).await?;
+
+ if response.status().is_client_error() {
+ let text = String::from_utf8_lossy(body.as_slice());
+ bail!(
+ "status error {}, response: {text:?}",
+ response.status().as_u16()
+ );
+ }
+
+ let body_str = std::str::from_utf8(&body)?;
+
+ serde_json::from_str::<CommitDetails>(body_str)
+ .map(|codeberg_commit| {
+ if let Some(author) = codeberg_commit.author {
+ Some(GitAuthor {
+ avatar_url: author.avatar_url,
+ })
+ } else {
+ None
+ }
+ })
+ .context("deserializing Codeberg commit details failed")
+}
@@ -0,0 +1,5 @@
+/// Represents the common denominator of most git hosting authors
+#[derive(Debug)]
+pub struct GitAuthor {
+ pub avatar_url: String,
+}
@@ -1,4 +1,4 @@
-use crate::http::HttpClient;
+use crate::{git_author::GitAuthor, http::HttpClient};
use anyhow::{anyhow, bail, Context, Result};
use futures::AsyncReadExt;
use isahc::{config::Configurable, AsyncBody, Request};
@@ -49,19 +49,12 @@ struct User {
pub avatar_url: String,
}
-#[derive(Debug)]
-pub struct GitHubAuthor {
- pub id: u64,
- pub email: String,
- pub avatar_url: String,
-}
-
pub async fn fetch_github_commit_author(
repo_owner: &str,
repo: &str,
commit: &str,
client: &Arc<dyn HttpClient>,
-) -> Result<Option<GitHubAuthor>> {
+) -> Result<Option<GitAuthor>> {
let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}");
let mut request = Request::get(&url)
@@ -93,10 +86,8 @@ pub async fn fetch_github_commit_author(
serde_json::from_str::<CommitDetails>(body_str)
.map(|github_commit| {
if let Some(author) = github_commit.author {
- Some(GitHubAuthor {
- id: author.id,
+ Some(GitAuthor {
avatar_url: author.avatar_url,
- email: github_commit.commit.author.email,
})
} else {
None
@@ -1,5 +1,7 @@
pub mod arc_cow;
+pub mod codeberg;
pub mod fs;
+mod git_author;
pub mod github;
pub mod http;
pub mod paths;