Allow configuring custom git hosting providers in project settings (#31929)
Closes #29229 Release Notes: - Extended the support for configuring custom git hosting providers to cover project settings in addition to global settings. --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
parent
203754d0db
commit
1307b81721
4 changed files with 138 additions and 20 deletions
|
@ -2,7 +2,6 @@ use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collections::BTreeMap;
|
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use gpui::{App, Global, SharedString};
|
use gpui::{App, Global, SharedString};
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
|
@ -130,7 +129,8 @@ impl Global for GlobalGitHostingProviderRegistry {}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct GitHostingProviderRegistryState {
|
struct GitHostingProviderRegistryState {
|
||||||
providers: BTreeMap<String, Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
|
default_providers: Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
|
||||||
|
setting_providers: Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -140,6 +140,7 @@ pub struct GitHostingProviderRegistry {
|
||||||
|
|
||||||
impl GitHostingProviderRegistry {
|
impl GitHostingProviderRegistry {
|
||||||
/// Returns the global [`GitHostingProviderRegistry`].
|
/// Returns the global [`GitHostingProviderRegistry`].
|
||||||
|
#[track_caller]
|
||||||
pub fn global(cx: &App) -> Arc<Self> {
|
pub fn global(cx: &App) -> Arc<Self> {
|
||||||
cx.global::<GlobalGitHostingProviderRegistry>().0.clone()
|
cx.global::<GlobalGitHostingProviderRegistry>().0.clone()
|
||||||
}
|
}
|
||||||
|
@ -168,7 +169,8 @@ impl GitHostingProviderRegistry {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: RwLock::new(GitHostingProviderRegistryState {
|
state: RwLock::new(GitHostingProviderRegistryState {
|
||||||
providers: BTreeMap::default(),
|
setting_providers: Vec::default(),
|
||||||
|
default_providers: Vec::default(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +179,22 @@ impl GitHostingProviderRegistry {
|
||||||
pub fn list_hosting_providers(
|
pub fn list_hosting_providers(
|
||||||
&self,
|
&self,
|
||||||
) -> Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>> {
|
) -> Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>> {
|
||||||
self.state.read().providers.values().cloned().collect()
|
let state = self.state.read();
|
||||||
|
state
|
||||||
|
.default_providers
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(state.setting_providers.iter().cloned())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_setting_providers(
|
||||||
|
&self,
|
||||||
|
providers: impl IntoIterator<Item = Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
|
||||||
|
) {
|
||||||
|
let mut state = self.state.write();
|
||||||
|
state.setting_providers.clear();
|
||||||
|
state.setting_providers.extend(providers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds the provided [`GitHostingProvider`] to the registry.
|
/// Adds the provided [`GitHostingProvider`] to the registry.
|
||||||
|
@ -185,10 +202,7 @@ impl GitHostingProviderRegistry {
|
||||||
&self,
|
&self,
|
||||||
provider: Arc<dyn GitHostingProvider + Send + Sync + 'static>,
|
provider: Arc<dyn GitHostingProvider + Send + Sync + 'static>,
|
||||||
) {
|
) {
|
||||||
self.state
|
self.state.write().default_providers.push(provider);
|
||||||
.write()
|
|
||||||
.providers
|
|
||||||
.insert(provider.name(), provider);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,22 +25,34 @@ fn init_git_hosting_provider_settings(cx: &mut App) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_git_hosting_providers_from_settings(cx: &mut App) {
|
fn update_git_hosting_providers_from_settings(cx: &mut App) {
|
||||||
|
let settings_store = cx.global::<SettingsStore>();
|
||||||
let settings = GitHostingProviderSettings::get_global(cx);
|
let settings = GitHostingProviderSettings::get_global(cx);
|
||||||
let provider_registry = GitHostingProviderRegistry::global(cx);
|
let provider_registry = GitHostingProviderRegistry::global(cx);
|
||||||
|
|
||||||
for provider in settings.git_hosting_providers.iter() {
|
let local_values: Vec<GitHostingProviderConfig> = settings_store
|
||||||
let Some(url) = Url::parse(&provider.base_url).log_err() else {
|
.get_all_locals::<GitHostingProviderSettings>()
|
||||||
continue;
|
.into_iter()
|
||||||
};
|
.flat_map(|(_, _, providers)| providers.git_hosting_providers.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let provider = match provider.provider {
|
let iter = settings
|
||||||
GitHostingProviderKind::Bitbucket => Arc::new(Bitbucket::new(&provider.name, url)) as _,
|
.git_hosting_providers
|
||||||
GitHostingProviderKind::Github => Arc::new(Github::new(&provider.name, url)) as _,
|
.clone()
|
||||||
GitHostingProviderKind::Gitlab => Arc::new(Gitlab::new(&provider.name, url)) as _,
|
.into_iter()
|
||||||
};
|
.chain(local_values)
|
||||||
|
.filter_map(|provider| {
|
||||||
|
let url = Url::parse(&provider.base_url).log_err()?;
|
||||||
|
|
||||||
provider_registry.register_hosting_provider(provider);
|
Some(match provider.provider {
|
||||||
}
|
GitHostingProviderKind::Bitbucket => {
|
||||||
|
Arc::new(Bitbucket::new(&provider.name, url)) as _
|
||||||
|
}
|
||||||
|
GitHostingProviderKind::Github => Arc::new(Github::new(&provider.name, url)) as _,
|
||||||
|
GitHostingProviderKind::Gitlab => Arc::new(Gitlab::new(&provider.name, url)) as _,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
provider_registry.set_setting_providers(iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
@ -66,7 +78,7 @@ pub struct GitHostingProviderConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct GitHostingProviderSettings {
|
pub struct GitHostingProviderSettings {
|
||||||
/// The list of custom Git hosting providers.
|
/// The list of custom Git hosting providers.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
|
@ -11,6 +11,7 @@ use buffer_diff::{
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use futures::{StreamExt, future};
|
use futures::{StreamExt, future};
|
||||||
use git::{
|
use git::{
|
||||||
|
GitHostingProviderRegistry,
|
||||||
repository::RepoPath,
|
repository::RepoPath,
|
||||||
status::{StatusCode, TrackedStatus},
|
status::{StatusCode, TrackedStatus},
|
||||||
};
|
};
|
||||||
|
@ -216,6 +217,71 @@ async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_git_provider_project_setting(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
cx.update(|cx| {
|
||||||
|
GitHostingProviderRegistry::default_global(cx);
|
||||||
|
git_hosting_providers::init(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
let str_path = path!("/dir");
|
||||||
|
let path = Path::new(str_path);
|
||||||
|
|
||||||
|
fs.insert_tree(
|
||||||
|
path!("/dir"),
|
||||||
|
json!({
|
||||||
|
".zed": {
|
||||||
|
"settings.json": r#"{
|
||||||
|
"git_hosting_providers": [
|
||||||
|
{
|
||||||
|
"provider": "gitlab",
|
||||||
|
"base_url": "https://google.com",
|
||||||
|
"name": "foo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"#
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||||
|
let (_worktree, _) =
|
||||||
|
project.read_with(cx, |project, cx| project.find_worktree(path, cx).unwrap());
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
let provider = GitHostingProviderRegistry::global(cx);
|
||||||
|
assert!(
|
||||||
|
provider
|
||||||
|
.list_hosting_providers()
|
||||||
|
.into_iter()
|
||||||
|
.any(|provider| provider.name() == "foo")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.atomic_write(
|
||||||
|
Path::new(path!("/dir/.zed/settings.json")).to_owned(),
|
||||||
|
"{}".into(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
let provider = GitHostingProviderRegistry::global(cx);
|
||||||
|
assert!(
|
||||||
|
!provider
|
||||||
|
.list_hosting_providers()
|
||||||
|
.into_iter()
|
||||||
|
.any(|provider| provider.name() == "foo")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
|
async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|
|
@ -250,6 +250,7 @@ trait AnySettingValue: 'static + Send + Sync {
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Result<Box<dyn Any>>;
|
) -> Result<Box<dyn Any>>;
|
||||||
fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
|
fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
|
||||||
|
fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)>;
|
||||||
fn set_global_value(&mut self, value: Box<dyn Any>);
|
fn set_global_value(&mut self, value: Box<dyn Any>);
|
||||||
fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>);
|
fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>);
|
||||||
fn json_schema(
|
fn json_schema(
|
||||||
|
@ -376,6 +377,24 @@ impl SettingsStore {
|
||||||
.expect("no default value for setting type")
|
.expect("no default value for setting type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all values from project specific settings
|
||||||
|
pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<Path>, &T)> {
|
||||||
|
self.setting_values
|
||||||
|
.get(&TypeId::of::<T>())
|
||||||
|
.unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
|
||||||
|
.all_local_values()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, path, any)| {
|
||||||
|
(
|
||||||
|
id,
|
||||||
|
path,
|
||||||
|
any.downcast_ref::<T>()
|
||||||
|
.expect("wrong value type for setting"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Override the global value for a setting.
|
/// Override the global value for a setting.
|
||||||
///
|
///
|
||||||
/// The given value will be overwritten if the user settings file changes.
|
/// The given value will be overwritten if the user settings file changes.
|
||||||
|
@ -1235,6 +1254,13 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
|
||||||
(key, value)
|
(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)> {
|
||||||
|
self.local_values
|
||||||
|
.iter()
|
||||||
|
.map(|(id, path, value)| (*id, path.clone(), value as _))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
|
fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any {
|
||||||
if let Some(SettingsLocation { worktree_id, path }) = path {
|
if let Some(SettingsLocation { worktree_id, path }) = path {
|
||||||
for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
|
for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue