Add a registry for GitHostingProviders (#11470)

This PR adds a registry for `GitHostingProvider`s.

The intent here is to help decouple these provider-specific concerns
from the lower-level `git` crate.

Similar to languages, the Git hosting providers live in the new
`git_hosting_providers` crate.

This work also lays the foundation for if we wanted to allow defining a
`GitHostingProvider` from within an extension. This could be useful if
we wanted to extend the support to work with self-hosted Git providers
(like GitHub Enterprise).

I also took the opportunity to move some of the provider-specific code
out of the `util` crate, since it had leaked into there.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-05-06 21:24:48 -04:00 committed by GitHub
parent a64e20ed96
commit 88c4e0b2d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 405 additions and 229 deletions

View file

@ -1,78 +0,0 @@
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")
}

View file

@ -1,5 +0,0 @@
/// Represents the common denominator of most git hosting authors
#[derive(Debug)]
pub struct GitAuthor {
pub avatar_url: String,
}

View file

@ -1,7 +1,6 @@
use crate::{git_author::GitAuthor, http::HttpClient};
use crate::http::HttpClient;
use anyhow::{anyhow, bail, Context, Result};
use futures::AsyncReadExt;
use isahc::{config::Configurable, AsyncBody, Request};
use serde::Deserialize;
use std::sync::Arc;
use url::Url;
@ -27,75 +26,6 @@ pub struct GithubReleaseAsset {
pub browser_download_url: String,
}
#[derive(Debug, Deserialize)]
struct CommitDetails {
commit: Commit,
author: Option<User>,
}
#[derive(Debug, Deserialize)]
struct Commit {
author: Author,
}
#[derive(Debug, Deserialize)]
struct Author {
email: String,
}
#[derive(Debug, Deserialize)]
struct User {
pub id: u64,
pub avatar_url: String,
}
pub async fn fetch_github_commit_author(
repo_owner: &str,
repo: &str,
commit: &str,
client: &Arc<dyn HttpClient>,
) -> Result<Option<GitAuthor>> {
let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}");
let mut request = Request::get(&url)
.redirect_policy(isahc::config::RedirectPolicy::Follow)
.header("Content-Type", "application/json");
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
request = request.header("Authorization", format!("Bearer {}", github_token));
}
let mut response = client
.send(request.body(AsyncBody::default())?)
.await
.with_context(|| format!("error fetching GitHub 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(|github_commit| {
if let Some(author) = github_commit.author {
Some(GitAuthor {
avatar_url: author.avatar_url,
})
} else {
None
}
})
.context("deserializing GitHub commit details failed")
}
pub async fn latest_github_release(
repo_name_with_owner: &str,
require_assets: bool,

View file

@ -1,7 +1,5 @@
pub mod arc_cow;
pub mod codeberg;
pub mod fs;
mod git_author;
pub mod github;
pub mod http;
pub mod paths;