@@ -4915,6 +4915,7 @@ dependencies = [
"serde_json",
"unindent",
"url",
+ "util",
]
[[package]]
@@ -14730,6 +14731,7 @@ dependencies = [
"fuzzy",
"git",
"git2",
+ "git_hosting_providers",
"gpui",
"http_client",
"ignore",
@@ -2,6 +2,7 @@ mod providers;
use std::sync::Arc;
+use git::repository::GitRepository;
use git::GitHostingProviderRegistry;
use gpui::AppContext;
@@ -10,17 +11,27 @@ pub use crate::providers::*;
/// Initializes the Git hosting providers.
pub fn init(cx: &AppContext) {
let provider_registry = GitHostingProviderRegistry::global(cx);
-
- // The providers are stored in a `BTreeMap`, so insertion order matters.
- // GitHub comes first.
+ provider_registry.register_hosting_provider(Arc::new(Bitbucket));
+ provider_registry.register_hosting_provider(Arc::new(Codeberg));
+ provider_registry.register_hosting_provider(Arc::new(Gitee));
provider_registry.register_hosting_provider(Arc::new(Github));
+ provider_registry.register_hosting_provider(Arc::new(Gitlab::new()));
+ provider_registry.register_hosting_provider(Arc::new(Sourcehut));
+}
- // Then GitLab.
- provider_registry.register_hosting_provider(Arc::new(Gitlab));
+/// Registers additional Git hosting providers.
+///
+/// These require information from the Git repository to construct, so their
+/// registration is deferred until we have a Git repository initialized.
+pub fn register_additional_providers(
+ provider_registry: Arc<GitHostingProviderRegistry>,
+ repository: Arc<dyn GitRepository>,
+) {
+ let Some(origin_url) = repository.remote_url("origin") else {
+ return;
+ };
- // Then the other providers, in the order they were added.
- provider_registry.register_hosting_provider(Arc::new(Gitee));
- provider_registry.register_hosting_provider(Arc::new(Bitbucket));
- provider_registry.register_hosting_provider(Arc::new(Sourcehut));
- provider_registry.register_hosting_provider(Arc::new(Codeberg));
+ if let Ok(gitlab_self_hosted) = Gitlab::from_remote_url(&origin_url) {
+ provider_registry.register_hosting_provider(Arc::new(gitlab_self_hosted));
+ }
}
@@ -1,16 +1,55 @@
+use anyhow::{anyhow, bail, Result};
use url::Url;
+use util::maybe;
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
-pub struct Gitlab;
+#[derive(Debug)]
+pub struct Gitlab {
+ name: String,
+ base_url: Url,
+}
+
+impl Gitlab {
+ pub fn new() -> Self {
+ Self {
+ name: "GitLab".to_string(),
+ base_url: Url::parse("https://gitlab.com").unwrap(),
+ }
+ }
+
+ pub fn from_remote_url(remote_url: &str) -> Result<Self> {
+ let host = maybe!({
+ if let Some(remote_url) = remote_url.strip_prefix("git@") {
+ if let Some((host, _)) = remote_url.trim_start_matches("git@").split_once(':') {
+ return Some(host.to_string());
+ }
+ }
+
+ Url::parse(&remote_url)
+ .ok()
+ .and_then(|remote_url| remote_url.host_str().map(|host| host.to_string()))
+ })
+ .ok_or_else(|| anyhow!("URL has no host"))?;
+
+ if !host.contains("gitlab") {
+ bail!("not a GitLab URL");
+ }
+
+ Ok(Self {
+ name: "GitLab Self-Hosted".to_string(),
+ base_url: Url::parse(&format!("https://{}", host))?,
+ })
+ }
+}
impl GitHostingProvider for Gitlab {
fn name(&self) -> String {
- "GitLab".to_string()
+ self.name.clone()
}
fn base_url(&self) -> Url {
- Url::parse("https://gitlab.com").unwrap()
+ self.base_url.clone()
}
fn supports_avatars(&self) -> bool {
@@ -26,10 +65,12 @@ impl GitHostingProvider for Gitlab {
}
fn parse_remote_url<'a>(&self, url: &'a str) -> Option<ParsedGitRemote<'a>> {
- if url.starts_with("git@gitlab.com:") || url.starts_with("https://gitlab.com/") {
+ let host = self.base_url.host_str()?;
+
+ if url.starts_with(&format!("git@{host}")) || url.starts_with(&format!("https://{host}/")) {
let repo_with_owner = url
- .trim_start_matches("git@gitlab.com:")
- .trim_start_matches("https://gitlab.com/")
+ .trim_start_matches(&format!("git@{host}:"))
+ .trim_start_matches(&format!("https://{host}/"))
.trim_end_matches(".git");
let (owner, repo) = repo_with_owner.split_once('/')?;
@@ -79,6 +120,8 @@ impl GitHostingProvider for Gitlab {
#[cfg(test)]
mod tests {
+ use pretty_assertions::assert_eq;
+
use super::*;
#[test]
@@ -87,7 +130,7 @@ mod tests {
owner: "zed-industries",
repo: "zed",
};
- let permalink = Gitlab.build_permalink(
+ let permalink = Gitlab::new().build_permalink(
remote,
BuildPermalinkParams {
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
@@ -106,7 +149,7 @@ mod tests {
owner: "zed-industries",
repo: "zed",
};
- let permalink = Gitlab.build_permalink(
+ let permalink = Gitlab::new().build_permalink(
remote,
BuildPermalinkParams {
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
@@ -125,7 +168,7 @@ mod tests {
owner: "zed-industries",
repo: "zed",
};
- let permalink = Gitlab.build_permalink(
+ let permalink = Gitlab::new().build_permalink(
remote,
BuildPermalinkParams {
sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
@@ -144,7 +187,7 @@ mod tests {
owner: "zed-industries",
repo: "zed",
};
- let permalink = Gitlab.build_permalink(
+ let permalink = Gitlab::new().build_permalink(
remote,
BuildPermalinkParams {
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
@@ -163,7 +206,7 @@ mod tests {
owner: "zed-industries",
repo: "zed",
};
- let permalink = Gitlab.build_permalink(
+ let permalink = Gitlab::new().build_permalink(
remote,
BuildPermalinkParams {
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
@@ -182,7 +225,7 @@ mod tests {
owner: "zed-industries",
repo: "zed",
};
- let permalink = Gitlab.build_permalink(
+ let permalink = Gitlab::new().build_permalink(
remote,
BuildPermalinkParams {
sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
@@ -194,4 +237,48 @@ mod tests {
let expected_url = "https://gitlab.com/zed-industries/zed/-/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs#L24-48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
+
+ #[test]
+ fn test_build_gitlab_self_hosted_permalink_from_ssh_url() {
+ let remote = ParsedGitRemote {
+ owner: "zed-industries",
+ repo: "zed",
+ };
+ let gitlab =
+ Gitlab::from_remote_url("git@gitlab.some-enterprise.com:zed-industries/zed.git")
+ .unwrap();
+ let permalink = gitlab.build_permalink(
+ remote,
+ BuildPermalinkParams {
+ sha: "e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7",
+ path: "crates/editor/src/git/permalink.rs",
+ selection: None,
+ },
+ );
+
+ let expected_url = "https://gitlab.some-enterprise.com/zed-industries/zed/-/blob/e6ebe7974deb6bb6cc0e2595c8ec31f0c71084b7/crates/editor/src/git/permalink.rs";
+ assert_eq!(permalink.to_string(), expected_url.to_string())
+ }
+
+ #[test]
+ fn test_build_gitlab_self_hosted_permalink_from_https_url() {
+ let remote = ParsedGitRemote {
+ owner: "zed-industries",
+ repo: "zed",
+ };
+ let gitlab =
+ Gitlab::from_remote_url("https://gitlab-instance.big-co.com/zed-industries/zed.git")
+ .unwrap();
+ let permalink = gitlab.build_permalink(
+ remote,
+ BuildPermalinkParams {
+ sha: "b2efec9824c45fcc90c9a7eb107a50d1772a60aa",
+ path: "crates/zed/src/main.rs",
+ selection: None,
+ },
+ );
+
+ let expected_url = "https://gitlab-instance.big-co.com/zed-industries/zed/-/blob/b2efec9824c45fcc90c9a7eb107a50d1772a60aa/crates/zed/src/main.rs";
+ assert_eq!(permalink.to_string(), expected_url.to_string())
+ }
}
@@ -19,6 +19,7 @@ use futures::{
FutureExt as _, Stream, StreamExt,
};
use fuzzy::CharBag;
+use git::GitHostingProviderRegistry;
use git::{
repository::{GitFileStatus, GitRepository, RepoPath},
status::GitStatus,
@@ -299,6 +300,7 @@ struct BackgroundScannerState {
removed_entries: HashMap<u64, Entry>,
changed_paths: Vec<Arc<Path>>,
prev_snapshot: Snapshot,
+ git_hosting_provider_registry: Option<Arc<GitHostingProviderRegistry>>,
}
#[derive(Debug, Clone)]
@@ -1004,6 +1006,7 @@ impl LocalWorktree {
let share_private_files = self.share_private_files;
let next_entry_id = self.next_entry_id.clone();
let fs = self.fs.clone();
+ let git_hosting_provider_registry = GitHostingProviderRegistry::try_global(cx);
let settings = self.settings.clone();
let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
let background_scanner = cx.background_executor().spawn({
@@ -1039,6 +1042,7 @@ impl LocalWorktree {
paths_to_scan: Default::default(),
removed_entries: Default::default(),
changed_paths: Default::default(),
+ git_hosting_provider_registry,
}),
phase: BackgroundScannerPhase::InitialScan,
share_private_files,
@@ -2948,6 +2952,13 @@ impl BackgroundScannerState {
log::trace!("constructed libgit2 repo in {:?}", t0.elapsed());
let work_directory = RepositoryWorkDirectory(work_dir_path.clone());
+ if let Some(git_hosting_provider_registry) = self.git_hosting_provider_registry.clone() {
+ git_hosting_providers::register_additional_providers(
+ git_hosting_provider_registry,
+ repository.clone(),
+ );
+ }
+
self.snapshot.repository_entries.insert(
work_directory.clone(),
RepositoryEntry {