mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
git: Improve self-hosted provider support and Bitbucket integration (#42343)
This PR includes several minor modifications and improvements related to
Git hosting providers, covering the following areas:
1. Bitbucket Owner Parsing Fix: Remove the common `scm` prefix from the
remote URL of self-hosted Bitbucket instances to prevent incorrect owner
parsing.
[Reference](a6e3c6fbb2/src/git/remotes/bitbucket-server.ts (L72-L74))
2. Bitbucket Avatars in Blame: Add support for displaying Bitbucket
avatars in the Git blame view.
<img width="2750" height="1994" alt="CleanShot 2025-11-10 at 20 34
40@2x"
src="https://github.com/user-attachments/assets/9e26abdf-7880-4085-b636-a1f99ebeeb97"
/>
3. Self-hosted SourceHut Support: Add support for self-hosted SourceHut
instances.
4. Configuration: Add recently introduced self-hosted Git providers
(Gitea, Forgejo, and SourceHut) to the `git_hosting_providers` setting
option.
<img width="2750" height="1994" alt="CleanShot 2025-11-10 at 20 33
48@2x"
src="https://github.com/user-attachments/assets/44ffc799-182d-4145-9b89-e509bbc08843"
/>
Closes #11043
Release Notes:
- Improved self-hosted git provider support and Bitbucket integration
This commit is contained in:
parent
bc17491527
commit
b948d8b9e7
7 changed files with 354 additions and 21 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -7008,6 +7008,7 @@ dependencies = [
|
|||
"gpui",
|
||||
"http_client",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ futures.workspace = true
|
|||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
itertools.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ pub fn init(cx: &mut App) {
|
|||
provider_registry.register_hosting_provider(Arc::new(Gitee));
|
||||
provider_registry.register_hosting_provider(Arc::new(Github::public_instance()));
|
||||
provider_registry.register_hosting_provider(Arc::new(Gitlab::public_instance()));
|
||||
provider_registry.register_hosting_provider(Arc::new(Sourcehut));
|
||||
provider_registry.register_hosting_provider(Arc::new(SourceHut::public_instance()));
|
||||
}
|
||||
|
||||
/// Registers additional Git hosting providers.
|
||||
|
|
@ -51,6 +51,8 @@ pub async fn register_additional_providers(
|
|||
provider_registry.register_hosting_provider(Arc::new(gitea_self_hosted));
|
||||
} else if let Ok(bitbucket_self_hosted) = Bitbucket::from_remote_url(&origin_url) {
|
||||
provider_registry.register_hosting_provider(Arc::new(bitbucket_self_hosted));
|
||||
} else if let Ok(sourcehut_self_hosted) = SourceHut::from_remote_url(&origin_url) {
|
||||
provider_registry.register_hosting_provider(Arc::new(sourcehut_self_hosted));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::SharedString;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
|
||||
use itertools::Itertools as _;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use git::{
|
||||
|
|
@ -20,6 +26,42 @@ fn pull_request_regex() -> &'static Regex {
|
|||
&PULL_REQUEST_REGEX
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CommitDetails {
|
||||
author: Author,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Author {
|
||||
user: Account,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Account {
|
||||
links: AccountLinks,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AccountLinks {
|
||||
avatar: Option<Link>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Link {
|
||||
href: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CommitDetailsSelfHosted {
|
||||
author: AuthorSelfHosted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AuthorSelfHosted {
|
||||
avatar_url: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Bitbucket {
|
||||
name: String,
|
||||
base_url: Url,
|
||||
|
|
@ -61,8 +103,60 @@ impl Bitbucket {
|
|||
.host_str()
|
||||
.is_some_and(|host| host != "bitbucket.org")
|
||||
}
|
||||
|
||||
async fn fetch_bitbucket_commit_author(
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: &str,
|
||||
client: &Arc<dyn HttpClient>,
|
||||
) -> Result<Option<String>> {
|
||||
let Some(host) = self.base_url.host_str() else {
|
||||
bail!("failed to get host from bitbucket base url");
|
||||
};
|
||||
let is_self_hosted = self.is_self_hosted();
|
||||
let url = if is_self_hosted {
|
||||
format!(
|
||||
"https://{host}/rest/api/latest/projects/{repo_owner}/repos/{repo}/commits/{commit}?avatarSize=128"
|
||||
)
|
||||
} else {
|
||||
format!("https://api.{host}/2.0/repositories/{repo_owner}/{repo}/commit/{commit}")
|
||||
};
|
||||
|
||||
let request = Request::get(&url)
|
||||
.header("Content-Type", "application/json")
|
||||
.follow_redirects(http_client::RedirectPolicy::FollowAll);
|
||||
|
||||
let mut response = client
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.await
|
||||
.with_context(|| format!("error fetching BitBucket 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)?;
|
||||
|
||||
if is_self_hosted {
|
||||
serde_json::from_str::<CommitDetailsSelfHosted>(body_str)
|
||||
.map(|commit| commit.author.avatar_url)
|
||||
} else {
|
||||
serde_json::from_str::<CommitDetails>(body_str)
|
||||
.map(|commit| commit.author.user.links.avatar.map(|link| link.href))
|
||||
}
|
||||
.context("failed to deserialize BitBucket commit details")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GitHostingProvider for Bitbucket {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
|
|
@ -73,7 +167,7 @@ impl GitHostingProvider for Bitbucket {
|
|||
}
|
||||
|
||||
fn supports_avatars(&self) -> bool {
|
||||
false
|
||||
true
|
||||
}
|
||||
|
||||
fn format_line_number(&self, line: u32) -> String {
|
||||
|
|
@ -98,9 +192,16 @@ impl GitHostingProvider for Bitbucket {
|
|||
return None;
|
||||
}
|
||||
|
||||
let mut path_segments = url.path_segments()?;
|
||||
let owner = path_segments.next()?;
|
||||
let repo = path_segments.next()?.trim_end_matches(".git");
|
||||
let mut path_segments = url.path_segments()?.collect::<Vec<_>>();
|
||||
let repo = path_segments.pop()?.trim_end_matches(".git");
|
||||
let owner = if path_segments.get(0).is_some_and(|v| *v == "scm") && path_segments.len() > 1
|
||||
{
|
||||
// Skip the "scm" segment if it's not the only segment
|
||||
// https://github.com/gitkraken/vscode-gitlens/blob/a6e3c6fbb255116507eaabaa9940c192ed7bb0e1/src/git/remotes/bitbucket-server.ts#L72-L74
|
||||
path_segments.into_iter().skip(1).join("/")
|
||||
} else {
|
||||
path_segments.into_iter().join("/")
|
||||
};
|
||||
|
||||
Some(ParsedGitRemote {
|
||||
owner: owner.into(),
|
||||
|
|
@ -176,6 +277,22 @@ impl GitHostingProvider for Bitbucket {
|
|||
|
||||
Some(PullRequest { number, url })
|
||||
}
|
||||
|
||||
async fn commit_author_avatar_url(
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: SharedString,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
let commit = commit.to_string();
|
||||
let avatar_url = self
|
||||
.fetch_bitbucket_commit_author(repo_owner, repo, &commit, &http_client)
|
||||
.await?
|
||||
.map(|avatar_url| Url::parse(&avatar_url))
|
||||
.transpose()?;
|
||||
Ok(avatar_url)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -264,6 +381,38 @@ mod tests {
|
|||
repo: "zed".into(),
|
||||
}
|
||||
);
|
||||
|
||||
// Test with "scm" in the path
|
||||
let remote_url = "https://bitbucket.company.com/scm/zed-industries/zed.git";
|
||||
|
||||
let parsed_remote = Bitbucket::from_remote_url(remote_url)
|
||||
.unwrap()
|
||||
.parse_remote_url(remote_url)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parsed_remote,
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
}
|
||||
);
|
||||
|
||||
// Test with only "scm" as owner
|
||||
let remote_url = "https://bitbucket.company.com/scm/zed.git";
|
||||
|
||||
let parsed_remote = Bitbucket::from_remote_url(remote_url)
|
||||
.unwrap()
|
||||
.parse_remote_url(remote_url)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parsed_remote,
|
||||
ParsedGitRemote {
|
||||
owner: "scm".into(),
|
||||
repo: "zed".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use url::Url;
|
||||
|
||||
use git::{
|
||||
|
|
@ -7,15 +8,52 @@ use git::{
|
|||
RemoteUrl,
|
||||
};
|
||||
|
||||
pub struct Sourcehut;
|
||||
use crate::get_host_from_git_remote_url;
|
||||
|
||||
impl GitHostingProvider for Sourcehut {
|
||||
pub struct SourceHut {
|
||||
name: String,
|
||||
base_url: Url,
|
||||
}
|
||||
|
||||
impl SourceHut {
|
||||
pub fn new(name: &str, base_url: Url) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
base_url,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public_instance() -> Self {
|
||||
Self::new("SourceHut", Url::parse("https://git.sr.ht").unwrap())
|
||||
}
|
||||
|
||||
pub fn from_remote_url(remote_url: &str) -> Result<Self> {
|
||||
let host = get_host_from_git_remote_url(remote_url)?;
|
||||
if host == "git.sr.ht" {
|
||||
bail!("the SourceHut instance is not self-hosted");
|
||||
}
|
||||
|
||||
// TODO: detecting self hosted instances by checking whether "sourcehut" is in the url or not
|
||||
// is not very reliable. See https://github.com/zed-industries/zed/issues/26393 for more
|
||||
// information.
|
||||
if !host.contains("sourcehut") {
|
||||
bail!("not a SourceHut URL");
|
||||
}
|
||||
|
||||
Ok(Self::new(
|
||||
"SourceHut Self-Hosted",
|
||||
Url::parse(&format!("https://{}", host))?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl GitHostingProvider for SourceHut {
|
||||
fn name(&self) -> String {
|
||||
"SourceHut".to_string()
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn base_url(&self) -> Url {
|
||||
Url::parse("https://git.sr.ht").unwrap()
|
||||
self.base_url.clone()
|
||||
}
|
||||
|
||||
fn supports_avatars(&self) -> bool {
|
||||
|
|
@ -34,7 +72,7 @@ impl GitHostingProvider for Sourcehut {
|
|||
let url = RemoteUrl::from_str(url).ok()?;
|
||||
|
||||
let host = url.host_str()?;
|
||||
if host != "git.sr.ht" {
|
||||
if host != self.base_url.host_str()? {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +134,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_remote_url_given_ssh_url() {
|
||||
let parsed_remote = Sourcehut
|
||||
let parsed_remote = SourceHut::public_instance()
|
||||
.parse_remote_url("git@git.sr.ht:~zed-industries/zed")
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -111,7 +149,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_remote_url_given_ssh_url_with_git_suffix() {
|
||||
let parsed_remote = Sourcehut
|
||||
let parsed_remote = SourceHut::public_instance()
|
||||
.parse_remote_url("git@git.sr.ht:~zed-industries/zed.git")
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -126,7 +164,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_remote_url_given_https_url() {
|
||||
let parsed_remote = Sourcehut
|
||||
let parsed_remote = SourceHut::public_instance()
|
||||
.parse_remote_url("https://git.sr.ht/~zed-industries/zed")
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -139,9 +177,63 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_remote_url_given_self_hosted_ssh_url() {
|
||||
let remote_url = "git@sourcehut.org:~zed-industries/zed";
|
||||
|
||||
let parsed_remote = SourceHut::from_remote_url(remote_url)
|
||||
.unwrap()
|
||||
.parse_remote_url(remote_url)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parsed_remote,
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_remote_url_given_self_hosted_ssh_url_with_git_suffix() {
|
||||
let remote_url = "git@sourcehut.org:~zed-industries/zed.git";
|
||||
|
||||
let parsed_remote = SourceHut::from_remote_url(remote_url)
|
||||
.unwrap()
|
||||
.parse_remote_url(remote_url)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parsed_remote,
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed.git".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_remote_url_given_self_hosted_https_url() {
|
||||
let remote_url = "https://sourcehut.org/~zed-industries/zed";
|
||||
|
||||
let parsed_remote = SourceHut::from_remote_url(remote_url)
|
||||
.unwrap()
|
||||
.parse_remote_url(remote_url)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parsed_remote,
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink() {
|
||||
let permalink = Sourcehut.build_permalink(
|
||||
let permalink = SourceHut::public_instance().build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
|
|
@ -159,7 +251,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_with_git_suffix() {
|
||||
let permalink = Sourcehut.build_permalink(
|
||||
let permalink = SourceHut::public_instance().build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed.git".into(),
|
||||
|
|
@ -175,9 +267,49 @@ mod tests {
|
|||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_self_hosted_permalink() {
|
||||
let permalink = SourceHut::from_remote_url("https://sourcehut.org/~zed-industries/zed")
|
||||
.unwrap()
|
||||
.build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
},
|
||||
BuildPermalinkParams::new(
|
||||
"faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
&repo_path("crates/editor/src/git/permalink.rs"),
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
let expected_url = "https://sourcehut.org/~zed-industries/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_self_hosted_permalink_with_git_suffix() {
|
||||
let permalink = SourceHut::from_remote_url("https://sourcehut.org/~zed-industries/zed.git")
|
||||
.unwrap()
|
||||
.build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed.git".into(),
|
||||
},
|
||||
BuildPermalinkParams::new(
|
||||
"faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
&repo_path("crates/editor/src/git/permalink.rs"),
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
let expected_url = "https://sourcehut.org/~zed-industries/zed.git/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_with_single_line_selection() {
|
||||
let permalink = Sourcehut.build_permalink(
|
||||
let permalink = SourceHut::public_instance().build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
|
|
@ -195,7 +327,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_with_multi_line_selection() {
|
||||
let permalink = Sourcehut.build_permalink(
|
||||
let permalink = SourceHut::public_instance().build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
|
|
@ -210,4 +342,44 @@ mod tests {
|
|||
let expected_url = "https://git.sr.ht/~zed-industries/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_self_hosted_permalink_with_single_line_selection() {
|
||||
let permalink = SourceHut::from_remote_url("https://sourcehut.org/~zed-industries/zed")
|
||||
.unwrap()
|
||||
.build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
},
|
||||
BuildPermalinkParams::new(
|
||||
"faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
&repo_path("crates/editor/src/git/permalink.rs"),
|
||||
Some(6..6),
|
||||
),
|
||||
);
|
||||
|
||||
let expected_url = "https://sourcehut.org/~zed-industries/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_self_hosted_permalink_with_multi_line_selection() {
|
||||
let permalink = SourceHut::from_remote_url("https://sourcehut.org/~zed-industries/zed")
|
||||
.unwrap()
|
||||
.build_permalink(
|
||||
ParsedGitRemote {
|
||||
owner: "zed-industries".into(),
|
||||
repo: "zed".into(),
|
||||
},
|
||||
BuildPermalinkParams::new(
|
||||
"faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
&repo_path("crates/editor/src/git/permalink.rs"),
|
||||
Some(23..47),
|
||||
),
|
||||
);
|
||||
|
||||
let expected_url = "https://sourcehut.org/~zed-industries/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use settings::{
|
|||
use url::Url;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{Bitbucket, Github, Gitlab};
|
||||
use crate::{Bitbucket, Forgejo, Gitea, Github, Gitlab, SourceHut};
|
||||
|
||||
pub(crate) fn init(cx: &mut App) {
|
||||
init_git_hosting_provider_settings(cx);
|
||||
|
|
@ -46,6 +46,11 @@ fn update_git_hosting_providers_from_settings(cx: &mut App) {
|
|||
}
|
||||
GitHostingProviderKind::Github => Arc::new(Github::new(&provider.name, url)) as _,
|
||||
GitHostingProviderKind::Gitlab => Arc::new(Gitlab::new(&provider.name, url)) as _,
|
||||
GitHostingProviderKind::Gitea => Arc::new(Gitea::new(&provider.name, url)) as _,
|
||||
GitHostingProviderKind::Forgejo => Arc::new(Forgejo::new(&provider.name, url)) as _,
|
||||
GitHostingProviderKind::SourceHut => {
|
||||
Arc::new(SourceHut::new(&provider.name, url)) as _
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -543,7 +543,7 @@ pub enum DiagnosticSeverityContent {
|
|||
pub struct GitHostingProviderConfig {
|
||||
/// The type of the provider.
|
||||
///
|
||||
/// Must be one of `github`, `gitlab`, or `bitbucket`.
|
||||
/// Must be one of `github`, `gitlab`, `bitbucket`, `gitea`, `forgejo`, or `source_hut`.
|
||||
pub provider: GitHostingProviderKind,
|
||||
|
||||
/// The base URL for the provider (e.g., "https://code.corp.big.com").
|
||||
|
|
@ -559,4 +559,7 @@ pub enum GitHostingProviderKind {
|
|||
Github,
|
||||
Gitlab,
|
||||
Bitbucket,
|
||||
Gitea,
|
||||
Forgejo,
|
||||
SourceHut,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue