mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Add a registry for GitHostingProvider
s (#11470)
This PR adds a registry for `GitHostingProvider`s. The intent here is to help decouple these provider-specific concerns from the lower-level `git` crate. Similar to languages, the Git hosting providers live in the new `git_hosting_providers` crate. This work also lays the foundation for if we wanted to allow defining a `GitHostingProvider` from within an extension. This could be useful if we wanted to extend the support to work with self-hosted Git providers (like GitHub Enterprise). I also took the opportunity to move some of the provider-specific code out of the `util` crate, since it had leaked into there. Release Notes: - N/A
This commit is contained in:
parent
a64e20ed96
commit
88c4e0b2d8
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -2286,6 +2286,7 @@ dependencies = [
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"git",
|
||||
"git_hosting_providers",
|
||||
"google_ai",
|
||||
"gpui",
|
||||
"headless",
|
||||
@ -4394,12 +4395,13 @@ dependencies = [
|
||||
"async-trait",
|
||||
"clock",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"git2",
|
||||
"gpui",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"rope",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -4426,6 +4428,25 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git_hosting_providers"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"futures 0.3.28",
|
||||
"git",
|
||||
"gpui",
|
||||
"isahc",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"unindent",
|
||||
"url",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
@ -12766,6 +12787,8 @@ dependencies = [
|
||||
"file_icons",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"git",
|
||||
"git_hosting_providers",
|
||||
"go_to_line",
|
||||
"gpui",
|
||||
"headless",
|
||||
|
@ -35,6 +35,7 @@ members = [
|
||||
"crates/fsevent",
|
||||
"crates/fuzzy",
|
||||
"crates/git",
|
||||
"crates/git_hosting_providers",
|
||||
"crates/go_to_line",
|
||||
"crates/google_ai",
|
||||
"crates/gpui",
|
||||
@ -174,6 +175,7 @@ fs = { path = "crates/fs" }
|
||||
fsevent = { path = "crates/fsevent" }
|
||||
fuzzy = { path = "crates/fuzzy" }
|
||||
git = { path = "crates/git" }
|
||||
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||
go_to_line = { path = "crates/go_to_line" }
|
||||
google_ai = { path = "crates/google_ai" }
|
||||
gpui = { path = "crates/gpui" }
|
||||
|
@ -83,6 +83,7 @@ env_logger.workspace = true
|
||||
file_finder.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
git = { workspace = true, features = ["test-support"] }
|
||||
git_hosting_providers.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
|
@ -17,6 +17,7 @@ use collab_ui::channel_view::ChannelView;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
use futures::{channel::oneshot, StreamExt as _};
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
@ -257,6 +258,11 @@ impl TestServer {
|
||||
})
|
||||
});
|
||||
|
||||
let git_hosting_provider_registry =
|
||||
cx.update(|cx| GitHostingProviderRegistry::default_global(cx));
|
||||
git_hosting_provider_registry
|
||||
.register_hosting_provider(Arc::new(git_hosting_providers::Github));
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
|
||||
|
@ -41,7 +41,7 @@ mod editor_tests;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
use ::git::diff::{DiffHunk, DiffHunkStatus};
|
||||
use ::git::{parse_git_remote_url, BuildPermalinkParams};
|
||||
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
|
||||
pub(crate) use actions::*;
|
||||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
@ -9548,7 +9548,8 @@ impl Editor {
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
let selection = selections.iter().peekable().next();
|
||||
|
||||
let (provider, remote) = parse_git_remote_url(&origin_url)
|
||||
let (provider, remote) =
|
||||
parse_git_remote_url(GitHostingProviderRegistry::default_global(cx), &origin_url)
|
||||
.ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
|
||||
|
||||
Ok(provider.build_permalink(
|
||||
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use git::{
|
||||
blame::{Blame, BlameEntry},
|
||||
parse_git_remote_url, GitHostingProvider, Oid, PullRequest,
|
||||
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid, PullRequest,
|
||||
};
|
||||
use gpui::{Model, ModelContext, Subscription, Task};
|
||||
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
|
||||
@ -330,6 +330,7 @@ impl GitBlame {
|
||||
let snapshot = self.buffer.read(cx).snapshot();
|
||||
let blame = self.project.read(cx).blame_buffer(&self.buffer, None, cx);
|
||||
let languages = self.project.read(cx).languages().clone();
|
||||
let provider_registry = GitHostingProviderRegistry::default_global(cx);
|
||||
|
||||
self.task = cx.spawn(|this, mut cx| async move {
|
||||
let result = cx
|
||||
@ -345,8 +346,13 @@ impl GitBlame {
|
||||
} = blame.await?;
|
||||
|
||||
let entries = build_blame_entry_sum_tree(entries, snapshot.max_point().row);
|
||||
let commit_details =
|
||||
parse_commit_messages(messages, remote_url, &permalinks, &languages)
|
||||
let commit_details = parse_commit_messages(
|
||||
messages,
|
||||
remote_url,
|
||||
&permalinks,
|
||||
provider_registry,
|
||||
&languages,
|
||||
)
|
||||
.await;
|
||||
|
||||
anyhow::Ok((entries, commit_details))
|
||||
@ -438,11 +444,14 @@ async fn parse_commit_messages(
|
||||
messages: impl IntoIterator<Item = (Oid, String)>,
|
||||
remote_url: Option<String>,
|
||||
deprecated_permalinks: &HashMap<Oid, Url>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
) -> HashMap<Oid, CommitDetails> {
|
||||
let mut commit_details = HashMap::default();
|
||||
|
||||
let parsed_remote_url = remote_url.as_deref().and_then(parse_git_remote_url);
|
||||
let parsed_remote_url = remote_url
|
||||
.as_deref()
|
||||
.and_then(|remote_url| parse_git_remote_url(provider_registry, remote_url));
|
||||
|
||||
for (oid, message) in messages {
|
||||
let parsed_message = parse_markdown(&message, &languages).await;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use git::GitHostingProviderRegistry;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
@ -117,12 +118,19 @@ pub struct Metadata {
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RealFs {
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl RealFs {
|
||||
pub fn new(git_binary_path: Option<PathBuf>) -> Self {
|
||||
Self { git_binary_path }
|
||||
pub fn new(
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
git_hosting_provider_registry,
|
||||
git_binary_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,6 +482,7 @@ impl Fs for RealFs {
|
||||
Arc::new(Mutex::new(RealGitRepository::new(
|
||||
libgit_repository,
|
||||
self.git_binary_path.clone(),
|
||||
self.git_hosting_provider_registry.clone(),
|
||||
)))
|
||||
})
|
||||
}
|
||||
|
@ -16,19 +16,20 @@ anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
git2.workspace = true
|
||||
gpui.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rope.workspace = true
|
||||
serde.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
serde.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
parking_lot.workspace = true
|
||||
windows.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::commit::get_messages;
|
||||
use crate::{parse_git_remote_url, BuildCommitPermalinkParams, Oid};
|
||||
use crate::{parse_git_remote_url, BuildCommitPermalinkParams, GitHostingProviderRegistry, Oid};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{HashMap, HashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
use std::{ops::Range, path::Path};
|
||||
use text::Rope;
|
||||
use time;
|
||||
@ -33,6 +34,7 @@ impl Blame {
|
||||
path: &Path,
|
||||
content: &Rope,
|
||||
remote_url: Option<String>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> Result<Self> {
|
||||
let output = run_git_blame(git_binary, working_directory, path, &content)?;
|
||||
let mut entries = parse_git_blame(&output)?;
|
||||
@ -40,7 +42,9 @@ impl Blame {
|
||||
|
||||
let mut permalinks = HashMap::default();
|
||||
let mut unique_shas = HashSet::default();
|
||||
let parsed_remote_url = remote_url.as_deref().and_then(parse_git_remote_url);
|
||||
let parsed_remote_url = remote_url
|
||||
.as_deref()
|
||||
.and_then(|remote_url| parse_git_remote_url(provider_registry, remote_url));
|
||||
|
||||
for entry in entries.iter_mut() {
|
||||
unique_shas.insert(entry.sha);
|
||||
|
@ -1,5 +1,4 @@
|
||||
mod hosting_provider;
|
||||
mod hosting_providers;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -11,7 +10,6 @@ pub use git2 as libgit;
|
||||
pub use lazy_static::lazy_static;
|
||||
|
||||
pub use crate::hosting_provider::*;
|
||||
pub use crate::hosting_providers::*;
|
||||
|
||||
pub mod blame;
|
||||
pub mod commit;
|
||||
|
@ -2,10 +2,13 @@ use std::{ops::Range, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use collections::BTreeMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{AppContext, Global};
|
||||
use parking_lot::RwLock;
|
||||
use url::Url;
|
||||
use util::http::HttpClient;
|
||||
|
||||
use crate::hosting_providers::{Bitbucket, Codeberg, Gitee, Github, Gitlab, Sourcehut};
|
||||
use crate::Oid;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
@ -87,6 +90,69 @@ pub trait GitHostingProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deref, DerefMut)]
|
||||
struct GlobalGitHostingProviderRegistry(Arc<GitHostingProviderRegistry>);
|
||||
|
||||
impl Global for GlobalGitHostingProviderRegistry {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GitHostingProviderRegistryState {
|
||||
providers: BTreeMap<String, Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GitHostingProviderRegistry {
|
||||
state: RwLock<GitHostingProviderRegistryState>,
|
||||
}
|
||||
|
||||
impl GitHostingProviderRegistry {
|
||||
/// Returns the global [`GitHostingProviderRegistry`].
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
cx.global::<GlobalGitHostingProviderRegistry>().0.clone()
|
||||
}
|
||||
|
||||
/// Returns the global [`GitHostingProviderRegistry`].
|
||||
///
|
||||
/// Inserts a default [`GitHostingProviderRegistry`] if one does not yet exist.
|
||||
pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
|
||||
cx.default_global::<GlobalGitHostingProviderRegistry>()
|
||||
.0
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Sets the global [`GitHostingProviderRegistry`].
|
||||
pub fn set_global(registry: Arc<GitHostingProviderRegistry>, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalGitHostingProviderRegistry(registry));
|
||||
}
|
||||
|
||||
/// Returns a new [`GitHostingProviderRegistry`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: RwLock::new(GitHostingProviderRegistryState {
|
||||
providers: BTreeMap::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of all [`GitHostingProvider`]s in the registry.
|
||||
pub fn list_hosting_providers(
|
||||
&self,
|
||||
) -> Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>> {
|
||||
self.state.read().providers.values().cloned().collect()
|
||||
}
|
||||
|
||||
/// Adds the provided [`GitHostingProvider`] to the registry.
|
||||
pub fn register_hosting_provider(
|
||||
&self,
|
||||
provider: Arc<dyn GitHostingProvider + Send + Sync + 'static>,
|
||||
) {
|
||||
self.state
|
||||
.write()
|
||||
.providers
|
||||
.insert(provider.name(), provider);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParsedGitRemote<'a> {
|
||||
pub owner: &'a str,
|
||||
@ -94,21 +160,16 @@ pub struct ParsedGitRemote<'a> {
|
||||
}
|
||||
|
||||
pub fn parse_git_remote_url(
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
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_registry
|
||||
.list_hosting_providers()
|
||||
.into_iter()
|
||||
.find_map(|provider| {
|
||||
provider
|
||||
.parse_remote_url(&url)
|
||||
.map(|parsed_remote| (provider, parsed_remote))
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::blame::Blame;
|
||||
use crate::GitHostingProviderRegistry;
|
||||
use anyhow::{Context, Result};
|
||||
use collections::HashMap;
|
||||
use git2::{BranchType, StatusShow};
|
||||
@ -71,13 +72,19 @@ impl std::fmt::Debug for dyn GitRepository {
|
||||
pub struct RealGitRepository {
|
||||
pub repository: LibGitRepository,
|
||||
pub git_binary_path: PathBuf,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
}
|
||||
|
||||
impl RealGitRepository {
|
||||
pub fn new(repository: LibGitRepository, git_binary_path: Option<PathBuf>) -> Self {
|
||||
pub fn new(
|
||||
repository: LibGitRepository,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
repository,
|
||||
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
||||
hosting_provider_registry,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -246,6 +253,7 @@ impl GitRepository for RealGitRepository {
|
||||
path,
|
||||
&content,
|
||||
remote_url,
|
||||
self.hosting_provider_registry.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
30
crates/git_hosting_providers/Cargo.toml
Normal file
30
crates/git_hosting_providers/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "git_hosting_providers"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/git_hosting_providers.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
isahc.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
unindent.workspace = true
|
||||
serde_json.workspace = true
|
||||
pretty_assertions.workspace = true
|
1
crates/git_hosting_providers/LICENSE-GPL
Symbolic link
1
crates/git_hosting_providers/LICENSE-GPL
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
26
crates/git_hosting_providers/src/git_hosting_providers.rs
Normal file
26
crates/git_hosting_providers/src/git_hosting_providers.rs
Normal file
@ -0,0 +1,26 @@
|
||||
mod providers;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::AppContext;
|
||||
|
||||
pub use crate::providers::*;
|
||||
|
||||
/// Initializes the Git hosting providers.
|
||||
pub fn init(cx: &mut 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(Github));
|
||||
|
||||
// Then GitLab.
|
||||
provider_registry.register_hosting_provider(Arc::new(Gitlab));
|
||||
|
||||
// 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));
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
};
|
||||
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||
|
||||
pub struct Bitbucket;
|
||||
|
||||
@ -77,14 +75,18 @@ impl GitHostingProvider for Bitbucket {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::parse_git_remote_url;
|
||||
use std::sync::Arc;
|
||||
|
||||
use git::{parse_git_remote_url, GitHostingProviderRegistry};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_git_remote_url_bitbucket_https_with_username() {
|
||||
let provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
provider_registry.register_hosting_provider(Arc::new(Bitbucket));
|
||||
let url = "https://thorstenballzed@bitbucket.org/thorstenzed/testingrepo.git";
|
||||
let (provider, parsed) = parse_git_remote_url(url).unwrap();
|
||||
let (provider, parsed) = parse_git_remote_url(provider_registry, url).unwrap();
|
||||
assert_eq!(provider.name(), "Bitbucket");
|
||||
assert_eq!(parsed.owner, "thorstenzed");
|
||||
assert_eq!(parsed.repo, "testingrepo");
|
||||
@ -92,8 +94,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_git_remote_url_bitbucket_https_without_username() {
|
||||
let provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
provider_registry.register_hosting_provider(Arc::new(Bitbucket));
|
||||
let url = "https://bitbucket.org/thorstenzed/testingrepo.git";
|
||||
let (provider, parsed) = parse_git_remote_url(url).unwrap();
|
||||
let (provider, parsed) = parse_git_remote_url(provider_registry, url).unwrap();
|
||||
assert_eq!(provider.name(), "Bitbucket");
|
||||
assert_eq!(parsed.owner, "thorstenzed");
|
||||
assert_eq!(parsed.repo, "testingrepo");
|
||||
@ -101,8 +105,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_git_remote_url_bitbucket_git() {
|
||||
let provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
provider_registry.register_hosting_provider(Arc::new(Bitbucket));
|
||||
let url = "git@bitbucket.org:thorstenzed/testingrepo.git";
|
||||
let (provider, parsed) = parse_git_remote_url(url).unwrap();
|
||||
let (provider, parsed) = parse_git_remote_url(provider_registry, url).unwrap();
|
||||
assert_eq!(provider.name(), "Bitbucket");
|
||||
assert_eq!(parsed.owner, "thorstenzed");
|
||||
assert_eq!(parsed.repo, "testingrepo");
|
@ -1,17 +1,88 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use isahc::config::Configurable;
|
||||
use isahc::{AsyncBody, Request};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
use util::codeberg;
|
||||
use util::http::HttpClient;
|
||||
|
||||
use crate::{
|
||||
use git::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CommitDetails {
|
||||
commit: Commit,
|
||||
author: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Commit {
|
||||
author: Author,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Author {
|
||||
name: String,
|
||||
email: String,
|
||||
date: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct User {
|
||||
pub login: String,
|
||||
pub id: u64,
|
||||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
pub struct Codeberg;
|
||||
|
||||
impl Codeberg {
|
||||
async fn fetch_codeberg_commit_author(
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: &str,
|
||||
client: &Arc<dyn HttpClient>,
|
||||
) -> Result<Option<User>> {
|
||||
let url =
|
||||
format!("https://codeberg.org/api/v1/repos/{repo_owner}/{repo}/git/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Ok(codeberg_token) = std::env::var("CODEBERG_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", codeberg_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.await
|
||||
.with_context(|| format!("error fetching Codeberg 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)?;
|
||||
|
||||
serde_json::from_str::<CommitDetails>(body_str)
|
||||
.map(|commit| commit.author)
|
||||
.context("failed to deserialize Codeberg commit details")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GitHostingProvider for Codeberg {
|
||||
fn name(&self) -> String {
|
||||
@ -90,8 +161,8 @@ impl GitHostingProvider for Codeberg {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
let commit = commit.to_string();
|
||||
let avatar_url =
|
||||
codeberg::fetch_codeberg_commit_author(repo_owner, repo, &commit, &http_client)
|
||||
let avatar_url = self
|
||||
.fetch_codeberg_commit_author(repo_owner, repo, &commit, &http_client)
|
||||
.await?
|
||||
.map(|author| Url::parse(&author.avatar_url))
|
||||
.transpose()?;
|
@ -1,8 +1,6 @@
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
};
|
||||
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||
|
||||
pub struct Gitee;
|
||||
|
@ -1,13 +1,16 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use isahc::config::Configurable;
|
||||
use isahc::{AsyncBody, Request};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
use util::github;
|
||||
use util::http::HttpClient;
|
||||
|
||||
use crate::{
|
||||
use git::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
||||
PullRequest,
|
||||
};
|
||||
@ -18,8 +21,72 @@ fn pull_request_number_regex() -> &'static Regex {
|
||||
PULL_REQUEST_NUMBER_REGEX.get_or_init(|| Regex::new(r"\(#(\d+)\)$").unwrap())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CommitDetails {
|
||||
commit: Commit,
|
||||
author: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Commit {
|
||||
author: Author,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Author {
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct User {
|
||||
pub id: u64,
|
||||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
pub struct Github;
|
||||
|
||||
impl Github {
|
||||
async fn fetch_github_commit_author(
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: &str,
|
||||
client: &Arc<dyn HttpClient>,
|
||||
) -> Result<Option<User>> {
|
||||
let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", github_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.await
|
||||
.with_context(|| format!("error fetching GitHub 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)?;
|
||||
|
||||
serde_json::from_str::<CommitDetails>(body_str)
|
||||
.map(|commit| commit.author)
|
||||
.context("failed to deserialize GitHub commit details")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GitHostingProvider for Github {
|
||||
fn name(&self) -> String {
|
||||
@ -110,8 +177,8 @@ impl GitHostingProvider for Github {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
let commit = commit.to_string();
|
||||
let avatar_url =
|
||||
github::fetch_github_commit_author(repo_owner, repo, &commit, &http_client)
|
||||
let avatar_url = self
|
||||
.fetch_github_commit_author(repo_owner, repo, &commit, &http_client)
|
||||
.await?
|
||||
.map(|author| -> Result<Url, url::ParseError> {
|
||||
let mut url = Url::parse(&author.avatar_url)?;
|
@ -1,8 +1,6 @@
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
};
|
||||
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||
|
||||
pub struct Gitlab;
|
||||
|
@ -1,8 +1,6 @@
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
};
|
||||
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||
|
||||
pub struct Sourcehut;
|
||||
|
@ -1,78 +0,0 @@
|
||||
use crate::{git_author::GitAuthor, http::HttpClient};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use futures::AsyncReadExt;
|
||||
use isahc::{config::Configurable, AsyncBody, Request};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CommitDetails {
|
||||
commit: Commit,
|
||||
author: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Commit {
|
||||
author: Author,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Author {
|
||||
name: String,
|
||||
email: String,
|
||||
date: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct User {
|
||||
pub login: String,
|
||||
pub id: u64,
|
||||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
pub async fn fetch_codeberg_commit_author(
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: &str,
|
||||
client: &Arc<dyn HttpClient>,
|
||||
) -> Result<Option<GitAuthor>> {
|
||||
let url = format!("https://codeberg.org/api/v1/repos/{repo_owner}/{repo}/git/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Ok(codeberg_token) = std::env::var("CODEBERG_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", codeberg_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.await
|
||||
.with_context(|| format!("error fetching Codeberg 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)?;
|
||||
|
||||
serde_json::from_str::<CommitDetails>(body_str)
|
||||
.map(|codeberg_commit| {
|
||||
if let Some(author) = codeberg_commit.author {
|
||||
Some(GitAuthor {
|
||||
avatar_url: author.avatar_url,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.context("deserializing Codeberg commit details failed")
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
/// Represents the common denominator of most git hosting authors
|
||||
#[derive(Debug)]
|
||||
pub struct GitAuthor {
|
||||
pub avatar_url: String,
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
use crate::{git_author::GitAuthor, http::HttpClient};
|
||||
use crate::http::HttpClient;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use futures::AsyncReadExt;
|
||||
use isahc::{config::Configurable, AsyncBody, Request};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
@ -27,75 +26,6 @@ pub struct GithubReleaseAsset {
|
||||
pub browser_download_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CommitDetails {
|
||||
commit: Commit,
|
||||
author: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Commit {
|
||||
author: Author,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Author {
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct User {
|
||||
pub id: u64,
|
||||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
pub async fn fetch_github_commit_author(
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: &str,
|
||||
client: &Arc<dyn HttpClient>,
|
||||
) -> Result<Option<GitAuthor>> {
|
||||
let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}");
|
||||
|
||||
let mut request = Request::get(&url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
|
||||
request = request.header("Authorization", format!("Bearer {}", github_token));
|
||||
}
|
||||
|
||||
let mut response = client
|
||||
.send(request.body(AsyncBody::default())?)
|
||||
.await
|
||||
.with_context(|| format!("error fetching GitHub 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)?;
|
||||
|
||||
serde_json::from_str::<CommitDetails>(body_str)
|
||||
.map(|github_commit| {
|
||||
if let Some(author) = github_commit.author {
|
||||
Some(GitAuthor {
|
||||
avatar_url: author.avatar_url,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.context("deserializing GitHub commit details failed")
|
||||
}
|
||||
|
||||
pub async fn latest_github_release(
|
||||
repo_name_with_owner: &str,
|
||||
require_assets: bool,
|
||||
|
@ -1,7 +1,5 @@
|
||||
pub mod arc_cow;
|
||||
pub mod codeberg;
|
||||
pub mod fs;
|
||||
mod git_author;
|
||||
pub mod github;
|
||||
pub mod http;
|
||||
pub mod paths;
|
||||
|
@ -46,6 +46,8 @@ file_icons.workspace = true
|
||||
file_finder.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
git_hosting_providers.workspace = true
|
||||
go_to_line.workspace = true
|
||||
gpui.workspace = true
|
||||
headless.workspace = true
|
||||
|
@ -16,6 +16,7 @@ use editor::Editor;
|
||||
use env_logger::Builder;
|
||||
use fs::RealFs;
|
||||
use futures::{future, StreamExt};
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::{App, AppContext, AsyncAppContext, Context, Task, VisualContext};
|
||||
use image_viewer;
|
||||
use language::LanguageRegistry;
|
||||
@ -119,6 +120,7 @@ fn init_headless(dev_server_token: DevServerToken) {
|
||||
project::Project::init(&client, cx);
|
||||
client::init(&client, cx);
|
||||
|
||||
let git_hosting_provider_registry = GitHostingProviderRegistry::default_global(cx);
|
||||
let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") {
|
||||
cx.path_for_auxiliary_executable("git")
|
||||
.context("could not find git binary path")
|
||||
@ -126,7 +128,9 @@ fn init_headless(dev_server_token: DevServerToken) {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let fs = Arc::new(RealFs::new(git_binary_path));
|
||||
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, git_binary_path));
|
||||
|
||||
git_hosting_providers::init(cx);
|
||||
|
||||
let mut languages =
|
||||
LanguageRegistry::new(Task::ready(()), cx.background_executor().clone());
|
||||
@ -186,6 +190,7 @@ fn init_ui(args: Args) {
|
||||
let session_id = Uuid::new_v4().to_string();
|
||||
reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
||||
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") {
|
||||
app.path_for_auxiliary_executable("git")
|
||||
.context("could not find git binary path")
|
||||
@ -195,7 +200,10 @@ fn init_ui(args: Args) {
|
||||
};
|
||||
log::info!("Using git binary path: {:?}", git_binary_path);
|
||||
|
||||
let fs = Arc::new(RealFs::new(git_binary_path));
|
||||
let fs = Arc::new(RealFs::new(
|
||||
git_hosting_provider_registry.clone(),
|
||||
git_binary_path,
|
||||
));
|
||||
let user_settings_file_rx = watch_config_file(
|
||||
&app.background_executor(),
|
||||
fs.clone(),
|
||||
@ -236,6 +244,9 @@ fn init_ui(args: Args) {
|
||||
AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
|
||||
}
|
||||
|
||||
GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
|
||||
git_hosting_providers::init(cx);
|
||||
|
||||
SystemAppearance::init(cx);
|
||||
OpenListener::set_global(listener.clone(), cx);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user