From f96cab286ca7d51e6fb49fbfbc6933897b47685c Mon Sep 17 00:00:00 2001 From: Stanislav Alekseev <43210583+WeetHet@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:57:10 +0300 Subject: [PATCH] Add avatar support for codeberg in git blame (#10991) Release Notes: - Added support for avatars in git blame for repositories hosted on codeberg Screenshot 2024-04-25 at 16 45 22 Questions: - Should we move git stuff like `Commit`, `Author`, etc outside of hosting-specific files (I don't think so, as other hostings can have different stuff) - Should we also add support for self hosted forgejo instances or should it be a different PR? --- crates/git/src/hosting_provider.rs | 35 ++++++++------ crates/util/src/codeberg.rs | 78 ++++++++++++++++++++++++++++++ crates/util/src/git_author.rs | 5 ++ crates/util/src/github.rs | 15 ++---- crates/util/src/util.rs | 2 + 5 files changed, 107 insertions(+), 28 deletions(-) create mode 100644 crates/util/src/codeberg.rs create mode 100644 crates/util/src/git_author.rs diff --git a/crates/git/src/hosting_provider.rs b/crates/git/src/hosting_provider.rs index eb7f9c5523..e51e5905c2 100644 --- a/crates/git/src/hosting_provider.rs +++ b/crates/git/src/hosting_provider.rs @@ -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, ) -> Result> { - 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 { + 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), - } + }?) } } diff --git a/crates/util/src/codeberg.rs b/crates/util/src/codeberg.rs new file mode 100644 index 0000000000..67cf76aaa8 --- /dev/null +++ b/crates/util/src/codeberg.rs @@ -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, +} + +#[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, +) -> Result> { + 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::(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") +} diff --git a/crates/util/src/git_author.rs b/crates/util/src/git_author.rs new file mode 100644 index 0000000000..b7172e6fcd --- /dev/null +++ b/crates/util/src/git_author.rs @@ -0,0 +1,5 @@ +/// Represents the common denominator of most git hosting authors +#[derive(Debug)] +pub struct GitAuthor { + pub avatar_url: String, +} diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs index 7d3c339bb4..f1ecce4732 100644 --- a/crates/util/src/github.rs +++ b/crates/util/src/github.rs @@ -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, -) -> Result> { +) -> Result> { 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::(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 diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 6202b91d84..82387af752 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -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;