Refactor Git hosting providers (#11457)
This PR refactors the code pertaining to Git hosting providers to make it more uniform and easy to add support for new providers. There is now a `GitHostingProvider` trait that contains the functionality specific to an individual Git hosting provider. Each provider we support has an implementation of this trait. Release Notes: - N/A
This commit is contained in:
parent
8871fec2a8
commit
bb1817ff31
17 changed files with 1443 additions and 883 deletions
|
@ -1,110 +1,116 @@
|
|||
use core::fmt;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use url::Url;
|
||||
use util::{codeberg, github, http::HttpClient};
|
||||
use util::http::HttpClient;
|
||||
|
||||
use crate::hosting_providers::{Bitbucket, Codeberg, Gitee, Github, Gitlab, Sourcehut};
|
||||
use crate::Oid;
|
||||
|
||||
#[derive(Clone, Debug, Hash)]
|
||||
pub enum HostingProvider {
|
||||
Github,
|
||||
Gitlab,
|
||||
Gitee,
|
||||
Bitbucket,
|
||||
Sourcehut,
|
||||
Codeberg,
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct PullRequest {
|
||||
pub number: u32,
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
impl HostingProvider {
|
||||
pub(crate) fn base_url(&self) -> Url {
|
||||
let base_url = match self {
|
||||
Self::Github => "https://github.com",
|
||||
Self::Gitlab => "https://gitlab.com",
|
||||
Self::Gitee => "https://gitee.com",
|
||||
Self::Bitbucket => "https://bitbucket.org",
|
||||
Self::Sourcehut => "https://git.sr.ht",
|
||||
Self::Codeberg => "https://codeberg.org",
|
||||
};
|
||||
pub struct BuildCommitPermalinkParams<'a> {
|
||||
pub sha: &'a str,
|
||||
}
|
||||
|
||||
Url::parse(&base_url).unwrap()
|
||||
}
|
||||
pub struct BuildPermalinkParams<'a> {
|
||||
pub sha: &'a str,
|
||||
pub path: &'a str,
|
||||
pub selection: Option<Range<u32>>,
|
||||
}
|
||||
|
||||
/// Returns the fragment portion of the URL for the selected lines in
|
||||
/// the representation the [`GitHostingProvider`] expects.
|
||||
pub(crate) fn line_fragment(&self, selection: &Range<u32>) -> String {
|
||||
/// A Git hosting provider.
|
||||
#[async_trait]
|
||||
pub trait GitHostingProvider {
|
||||
/// Returns the name of the provider.
|
||||
fn name(&self) -> String;
|
||||
|
||||
/// Returns the base URL of the provider.
|
||||
fn base_url(&self) -> Url;
|
||||
|
||||
/// Returns a permalink to a Git commit on this hosting provider.
|
||||
fn build_commit_permalink(
|
||||
&self,
|
||||
remote: &ParsedGitRemote,
|
||||
params: BuildCommitPermalinkParams,
|
||||
) -> Url;
|
||||
|
||||
/// Returns a permalink to a file and/or selection on this hosting provider.
|
||||
fn build_permalink(&self, remote: ParsedGitRemote, params: BuildPermalinkParams) -> Url;
|
||||
|
||||
/// Returns whether this provider supports avatars.
|
||||
fn supports_avatars(&self) -> bool;
|
||||
|
||||
/// Returns a URL fragment to the given line selection.
|
||||
fn line_fragment(&self, selection: &Range<u32>) -> String {
|
||||
if selection.start == selection.end {
|
||||
let line = selection.start + 1;
|
||||
|
||||
match self {
|
||||
Self::Github | Self::Gitlab | Self::Gitee | Self::Sourcehut | Self::Codeberg => {
|
||||
format!("L{}", line)
|
||||
}
|
||||
Self::Bitbucket => format!("lines-{}", line),
|
||||
}
|
||||
self.format_line_number(line)
|
||||
} else {
|
||||
let start_line = selection.start + 1;
|
||||
let end_line = selection.end + 1;
|
||||
|
||||
match self {
|
||||
Self::Github | Self::Codeberg => format!("L{}-L{}", start_line, end_line),
|
||||
Self::Gitlab | Self::Gitee | Self::Sourcehut => {
|
||||
format!("L{}-{}", start_line, end_line)
|
||||
}
|
||||
Self::Bitbucket => format!("lines-{}:{}", start_line, end_line),
|
||||
}
|
||||
self.format_line_numbers(start_line, end_line)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_avatars(&self) -> bool {
|
||||
match self {
|
||||
HostingProvider::Github | HostingProvider::Codeberg => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
/// Returns a formatted line number to be placed in a permalink URL.
|
||||
fn format_line_number(&self, line: u32) -> String;
|
||||
|
||||
pub async fn commit_author_avatar_url(
|
||||
/// Returns a formatted range of line numbers to be placed in a permalink URL.
|
||||
fn format_line_numbers(&self, start_line: u32, end_line: u32) -> String;
|
||||
|
||||
fn parse_remote_url<'a>(&self, url: &'a str) -> Option<ParsedGitRemote<'a>>;
|
||||
|
||||
fn extract_pull_request(
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: Oid,
|
||||
client: Arc<dyn HttpClient>,
|
||||
_remote: &ParsedGitRemote,
|
||||
_message: &str,
|
||||
) -> Option<PullRequest> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn commit_author_avatar_url(
|
||||
&self,
|
||||
_repo_owner: &str,
|
||||
_repo: &str,
|
||||
_commit: Oid,
|
||||
_http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
Ok(match self {
|
||||
HostingProvider::Github => {
|
||||
let commit = commit.to_string();
|
||||
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),
|
||||
}?)
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HostingProvider {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
HostingProvider::Github => "GitHub",
|
||||
HostingProvider::Gitlab => "GitLab",
|
||||
HostingProvider::Gitee => "Gitee",
|
||||
HostingProvider::Bitbucket => "Bitbucket",
|
||||
HostingProvider::Sourcehut => "Sourcehut",
|
||||
HostingProvider::Codeberg => "Codeberg",
|
||||
};
|
||||
write!(f, "{}", name)
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct ParsedGitRemote<'a> {
|
||||
pub owner: &'a str,
|
||||
pub repo: &'a str,
|
||||
}
|
||||
|
||||
pub fn parse_git_remote_url(
|
||||
url: &str,
|
||||
) -> Option<(
|
||||
Arc<dyn GitHostingProvider + Send + Sync + 'static>,
|
||||
ParsedGitRemote,
|
||||
)> {
|
||||
let providers: Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>> = vec![
|
||||
Arc::new(Github),
|
||||
Arc::new(Gitlab),
|
||||
Arc::new(Bitbucket),
|
||||
Arc::new(Codeberg),
|
||||
Arc::new(Gitee),
|
||||
Arc::new(Sourcehut),
|
||||
];
|
||||
|
||||
providers.into_iter().find_map(|provider| {
|
||||
provider
|
||||
.parse_remote_url(&url)
|
||||
.map(|parsed_remote| (provider, parsed_remote))
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue