Detailed changes
@@ -2,7 +2,6 @@ use std::{ops::Range, sync::Arc};
use anyhow::Result;
use async_trait::async_trait;
-use collections::BTreeMap;
use derive_more::{Deref, DerefMut};
use gpui::{App, Global, SharedString};
use http_client::HttpClient;
@@ -130,7 +129,8 @@ impl Global for GlobalGitHostingProviderRegistry {}
#[derive(Default)]
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)]
@@ -140,6 +140,7 @@ pub struct GitHostingProviderRegistry {
impl GitHostingProviderRegistry {
/// Returns the global [`GitHostingProviderRegistry`].
+ #[track_caller]
pub fn global(cx: &App) -> Arc<Self> {
cx.global::<GlobalGitHostingProviderRegistry>().0.clone()
}
@@ -168,7 +169,8 @@ impl GitHostingProviderRegistry {
pub fn new() -> Self {
Self {
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(
&self,
) -> 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.
@@ -185,10 +202,7 @@ impl GitHostingProviderRegistry {
&self,
provider: Arc<dyn GitHostingProvider + Send + Sync + 'static>,
) {
- self.state
- .write()
- .providers
- .insert(provider.name(), provider);
+ self.state.write().default_providers.push(provider);
}
}
@@ -25,22 +25,34 @@ fn init_git_hosting_provider_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 provider_registry = GitHostingProviderRegistry::global(cx);
- for provider in settings.git_hosting_providers.iter() {
- let Some(url) = Url::parse(&provider.base_url).log_err() else {
- continue;
- };
-
- let provider = 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.register_hosting_provider(provider);
- }
+ let local_values: Vec<GitHostingProviderConfig> = settings_store
+ .get_all_locals::<GitHostingProviderSettings>()
+ .into_iter()
+ .flat_map(|(_, _, providers)| providers.git_hosting_providers.clone())
+ .collect();
+
+ let iter = settings
+ .git_hosting_providers
+ .clone()
+ .into_iter()
+ .chain(local_values)
+ .filter_map(|provider| {
+ let url = Url::parse(&provider.base_url).log_err()?;
+
+ 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)]
@@ -66,7 +78,7 @@ pub struct GitHostingProviderConfig {
pub name: String,
}
-#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
+#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GitHostingProviderSettings {
/// The list of custom Git hosting providers.
#[serde(default)]
@@ -11,6 +11,7 @@ use buffer_diff::{
use fs::FakeFs;
use futures::{StreamExt, future};
use git::{
+ GitHostingProviderRegistry,
repository::RepoPath,
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]
async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
init_test(cx);
@@ -250,6 +250,7 @@ trait AnySettingValue: 'static + Send + Sync {
cx: &mut App,
) -> Result<Box<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_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>);
fn json_schema(
@@ -376,6 +377,24 @@ impl SettingsStore {
.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.
///
/// 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)
}
+ 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 {
if let Some(SettingsLocation { worktree_id, path }) = path {
for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {