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",
|
"fs",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
"git",
|
"git",
|
||||||
|
"git_hosting_providers",
|
||||||
"google_ai",
|
"google_ai",
|
||||||
"gpui",
|
"gpui",
|
||||||
"headless",
|
"headless",
|
||||||
@ -4394,12 +4395,13 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
|
"derive_more",
|
||||||
"git2",
|
"git2",
|
||||||
|
"gpui",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"regex",
|
|
||||||
"rope",
|
"rope",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -4426,6 +4428,25 @@ dependencies = [
|
|||||||
"url",
|
"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]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -12766,6 +12787,8 @@ dependencies = [
|
|||||||
"file_icons",
|
"file_icons",
|
||||||
"fs",
|
"fs",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
|
"git",
|
||||||
|
"git_hosting_providers",
|
||||||
"go_to_line",
|
"go_to_line",
|
||||||
"gpui",
|
"gpui",
|
||||||
"headless",
|
"headless",
|
||||||
|
@ -35,6 +35,7 @@ members = [
|
|||||||
"crates/fsevent",
|
"crates/fsevent",
|
||||||
"crates/fuzzy",
|
"crates/fuzzy",
|
||||||
"crates/git",
|
"crates/git",
|
||||||
|
"crates/git_hosting_providers",
|
||||||
"crates/go_to_line",
|
"crates/go_to_line",
|
||||||
"crates/google_ai",
|
"crates/google_ai",
|
||||||
"crates/gpui",
|
"crates/gpui",
|
||||||
@ -174,6 +175,7 @@ fs = { path = "crates/fs" }
|
|||||||
fsevent = { path = "crates/fsevent" }
|
fsevent = { path = "crates/fsevent" }
|
||||||
fuzzy = { path = "crates/fuzzy" }
|
fuzzy = { path = "crates/fuzzy" }
|
||||||
git = { path = "crates/git" }
|
git = { path = "crates/git" }
|
||||||
|
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||||
go_to_line = { path = "crates/go_to_line" }
|
go_to_line = { path = "crates/go_to_line" }
|
||||||
google_ai = { path = "crates/google_ai" }
|
google_ai = { path = "crates/google_ai" }
|
||||||
gpui = { path = "crates/gpui" }
|
gpui = { path = "crates/gpui" }
|
||||||
|
@ -83,6 +83,7 @@ env_logger.workspace = true
|
|||||||
file_finder.workspace = true
|
file_finder.workspace = true
|
||||||
fs = { workspace = true, features = ["test-support"] }
|
fs = { workspace = true, features = ["test-support"] }
|
||||||
git = { workspace = true, features = ["test-support"] }
|
git = { workspace = true, features = ["test-support"] }
|
||||||
|
git_hosting_providers.workspace = true
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
language = { workspace = true, features = ["test-support"] }
|
language = { workspace = true, features = ["test-support"] }
|
||||||
|
@ -17,6 +17,7 @@ use collab_ui::channel_view::ChannelView;
|
|||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use futures::{channel::oneshot, StreamExt as _};
|
use futures::{channel::oneshot, StreamExt as _};
|
||||||
|
use git::GitHostingProviderRegistry;
|
||||||
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
|
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use node_runtime::FakeNodeRuntime;
|
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 fs = FakeFs::new(cx.executor());
|
||||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||||
let workspace_store = cx.new_model(|cx| WorkspaceStore::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"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
use ::git::diff::{DiffHunk, DiffHunkStatus};
|
use ::git::diff::{DiffHunk, DiffHunkStatus};
|
||||||
use ::git::{parse_git_remote_url, BuildPermalinkParams};
|
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
|
||||||
pub(crate) use actions::*;
|
pub(crate) use actions::*;
|
||||||
use aho_corasick::AhoCorasick;
|
use aho_corasick::AhoCorasick;
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
@ -9548,8 +9548,9 @@ impl Editor {
|
|||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all::<Point>(cx);
|
||||||
let selection = selections.iter().peekable().next();
|
let selection = selections.iter().peekable().next();
|
||||||
|
|
||||||
let (provider, remote) = parse_git_remote_url(&origin_url)
|
let (provider, remote) =
|
||||||
.ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
|
parse_git_remote_url(GitHostingProviderRegistry::default_global(cx), &origin_url)
|
||||||
|
.ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
|
||||||
|
|
||||||
Ok(provider.build_permalink(
|
Ok(provider.build_permalink(
|
||||||
remote,
|
remote,
|
||||||
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use git::{
|
use git::{
|
||||||
blame::{Blame, BlameEntry},
|
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 gpui::{Model, ModelContext, Subscription, Task};
|
||||||
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
|
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
|
||||||
@ -330,6 +330,7 @@ impl GitBlame {
|
|||||||
let snapshot = self.buffer.read(cx).snapshot();
|
let snapshot = self.buffer.read(cx).snapshot();
|
||||||
let blame = self.project.read(cx).blame_buffer(&self.buffer, None, cx);
|
let blame = self.project.read(cx).blame_buffer(&self.buffer, None, cx);
|
||||||
let languages = self.project.read(cx).languages().clone();
|
let languages = self.project.read(cx).languages().clone();
|
||||||
|
let provider_registry = GitHostingProviderRegistry::default_global(cx);
|
||||||
|
|
||||||
self.task = cx.spawn(|this, mut cx| async move {
|
self.task = cx.spawn(|this, mut cx| async move {
|
||||||
let result = cx
|
let result = cx
|
||||||
@ -345,9 +346,14 @@ impl GitBlame {
|
|||||||
} = blame.await?;
|
} = blame.await?;
|
||||||
|
|
||||||
let entries = build_blame_entry_sum_tree(entries, snapshot.max_point().row);
|
let entries = build_blame_entry_sum_tree(entries, snapshot.max_point().row);
|
||||||
let commit_details =
|
let commit_details = parse_commit_messages(
|
||||||
parse_commit_messages(messages, remote_url, &permalinks, &languages)
|
messages,
|
||||||
.await;
|
remote_url,
|
||||||
|
&permalinks,
|
||||||
|
provider_registry,
|
||||||
|
&languages,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
anyhow::Ok((entries, commit_details))
|
anyhow::Ok((entries, commit_details))
|
||||||
}
|
}
|
||||||
@ -438,11 +444,14 @@ async fn parse_commit_messages(
|
|||||||
messages: impl IntoIterator<Item = (Oid, String)>,
|
messages: impl IntoIterator<Item = (Oid, String)>,
|
||||||
remote_url: Option<String>,
|
remote_url: Option<String>,
|
||||||
deprecated_permalinks: &HashMap<Oid, Url>,
|
deprecated_permalinks: &HashMap<Oid, Url>,
|
||||||
|
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
languages: &Arc<LanguageRegistry>,
|
languages: &Arc<LanguageRegistry>,
|
||||||
) -> HashMap<Oid, CommitDetails> {
|
) -> HashMap<Oid, CommitDetails> {
|
||||||
let mut commit_details = HashMap::default();
|
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 {
|
for (oid, message) in messages {
|
||||||
let parsed_message = parse_markdown(&message, &languages).await;
|
let parsed_message = parse_markdown(&message, &languages).await;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use git::GitHostingProviderRegistry;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
@ -117,12 +118,19 @@ pub struct Metadata {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RealFs {
|
pub struct RealFs {
|
||||||
|
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
git_binary_path: Option<PathBuf>,
|
git_binary_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RealFs {
|
impl RealFs {
|
||||||
pub fn new(git_binary_path: Option<PathBuf>) -> Self {
|
pub fn new(
|
||||||
Self { git_binary_path }
|
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(
|
Arc::new(Mutex::new(RealGitRepository::new(
|
||||||
libgit_repository,
|
libgit_repository,
|
||||||
self.git_binary_path.clone(),
|
self.git_binary_path.clone(),
|
||||||
|
self.git_hosting_provider_registry.clone(),
|
||||||
)))
|
)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -16,19 +16,20 @@ anyhow.workspace = true
|
|||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
derive_more.workspace = true
|
||||||
git2.workspace = true
|
git2.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
|
rope.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
sum_tree.workspace = true
|
sum_tree.workspace = true
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
serde.workspace = true
|
|
||||||
regex.workspace = true
|
|
||||||
rope.workspace = true
|
|
||||||
parking_lot.workspace = true
|
|
||||||
windows.workspace = true
|
windows.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::commit::get_messages;
|
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 anyhow::{anyhow, Context, Result};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{ops::Range, path::Path};
|
use std::{ops::Range, path::Path};
|
||||||
use text::Rope;
|
use text::Rope;
|
||||||
use time;
|
use time;
|
||||||
@ -33,6 +34,7 @@ impl Blame {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
content: &Rope,
|
content: &Rope,
|
||||||
remote_url: Option<String>,
|
remote_url: Option<String>,
|
||||||
|
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let output = run_git_blame(git_binary, working_directory, path, &content)?;
|
let output = run_git_blame(git_binary, working_directory, path, &content)?;
|
||||||
let mut entries = parse_git_blame(&output)?;
|
let mut entries = parse_git_blame(&output)?;
|
||||||
@ -40,7 +42,9 @@ impl Blame {
|
|||||||
|
|
||||||
let mut permalinks = HashMap::default();
|
let mut permalinks = HashMap::default();
|
||||||
let mut unique_shas = HashSet::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() {
|
for entry in entries.iter_mut() {
|
||||||
unique_shas.insert(entry.sha);
|
unique_shas.insert(entry.sha);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
mod hosting_provider;
|
mod hosting_provider;
|
||||||
mod hosting_providers;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -11,7 +10,6 @@ pub use git2 as libgit;
|
|||||||
pub use lazy_static::lazy_static;
|
pub use lazy_static::lazy_static;
|
||||||
|
|
||||||
pub use crate::hosting_provider::*;
|
pub use crate::hosting_provider::*;
|
||||||
pub use crate::hosting_providers::*;
|
|
||||||
|
|
||||||
pub mod blame;
|
pub mod blame;
|
||||||
pub mod commit;
|
pub mod commit;
|
||||||
|
@ -2,10 +2,13 @@ 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 gpui::{AppContext, Global};
|
||||||
|
use parking_lot::RwLock;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::http::HttpClient;
|
use util::http::HttpClient;
|
||||||
|
|
||||||
use crate::hosting_providers::{Bitbucket, Codeberg, Gitee, Github, Gitlab, Sourcehut};
|
|
||||||
use crate::Oid;
|
use crate::Oid;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[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)]
|
#[derive(Debug)]
|
||||||
pub struct ParsedGitRemote<'a> {
|
pub struct ParsedGitRemote<'a> {
|
||||||
pub owner: &'a str,
|
pub owner: &'a str,
|
||||||
@ -94,23 +160,18 @@ pub struct ParsedGitRemote<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_git_remote_url(
|
pub fn parse_git_remote_url(
|
||||||
|
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
url: &str,
|
url: &str,
|
||||||
) -> Option<(
|
) -> Option<(
|
||||||
Arc<dyn GitHostingProvider + Send + Sync + 'static>,
|
Arc<dyn GitHostingProvider + Send + Sync + 'static>,
|
||||||
ParsedGitRemote,
|
ParsedGitRemote,
|
||||||
)> {
|
)> {
|
||||||
let providers: Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>> = vec![
|
provider_registry
|
||||||
Arc::new(Github),
|
.list_hosting_providers()
|
||||||
Arc::new(Gitlab),
|
.into_iter()
|
||||||
Arc::new(Bitbucket),
|
.find_map(|provider| {
|
||||||
Arc::new(Codeberg),
|
provider
|
||||||
Arc::new(Gitee),
|
.parse_remote_url(&url)
|
||||||
Arc::new(Sourcehut),
|
.map(|parsed_remote| (provider, parsed_remote))
|
||||||
];
|
})
|
||||||
|
|
||||||
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::blame::Blame;
|
||||||
|
use crate::GitHostingProviderRegistry;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use git2::{BranchType, StatusShow};
|
use git2::{BranchType, StatusShow};
|
||||||
@ -71,13 +72,19 @@ impl std::fmt::Debug for dyn GitRepository {
|
|||||||
pub struct RealGitRepository {
|
pub struct RealGitRepository {
|
||||||
pub repository: LibGitRepository,
|
pub repository: LibGitRepository,
|
||||||
pub git_binary_path: PathBuf,
|
pub git_binary_path: PathBuf,
|
||||||
|
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RealGitRepository {
|
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 {
|
Self {
|
||||||
repository,
|
repository,
|
||||||
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
||||||
|
hosting_provider_registry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,6 +253,7 @@ impl GitRepository for RealGitRepository {
|
|||||||
path,
|
path,
|
||||||
&content,
|
&content,
|
||||||
remote_url,
|
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 url::Url;
|
||||||
|
|
||||||
use crate::{
|
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Bitbucket;
|
pub struct Bitbucket;
|
||||||
|
|
||||||
@ -77,14 +75,18 @@ impl GitHostingProvider for Bitbucket {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::parse_git_remote_url;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use git::{parse_git_remote_url, GitHostingProviderRegistry};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_git_remote_url_bitbucket_https_with_username() {
|
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 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!(provider.name(), "Bitbucket");
|
||||||
assert_eq!(parsed.owner, "thorstenzed");
|
assert_eq!(parsed.owner, "thorstenzed");
|
||||||
assert_eq!(parsed.repo, "testingrepo");
|
assert_eq!(parsed.repo, "testingrepo");
|
||||||
@ -92,8 +94,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_git_remote_url_bitbucket_https_without_username() {
|
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 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!(provider.name(), "Bitbucket");
|
||||||
assert_eq!(parsed.owner, "thorstenzed");
|
assert_eq!(parsed.owner, "thorstenzed");
|
||||||
assert_eq!(parsed.repo, "testingrepo");
|
assert_eq!(parsed.repo, "testingrepo");
|
||||||
@ -101,8 +105,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_git_remote_url_bitbucket_git() {
|
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 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!(provider.name(), "Bitbucket");
|
||||||
assert_eq!(parsed.owner, "thorstenzed");
|
assert_eq!(parsed.owner, "thorstenzed");
|
||||||
assert_eq!(parsed.repo, "testingrepo");
|
assert_eq!(parsed.repo, "testingrepo");
|
@ -1,17 +1,88 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use futures::AsyncReadExt;
|
||||||
|
use isahc::config::Configurable;
|
||||||
|
use isahc::{AsyncBody, Request};
|
||||||
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::codeberg;
|
|
||||||
use util::http::HttpClient;
|
use util::http::HttpClient;
|
||||||
|
|
||||||
use crate::{
|
use git::{
|
||||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
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;
|
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]
|
#[async_trait]
|
||||||
impl GitHostingProvider for Codeberg {
|
impl GitHostingProvider for Codeberg {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
@ -90,11 +161,11 @@ impl GitHostingProvider for Codeberg {
|
|||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
) -> Result<Option<Url>> {
|
) -> Result<Option<Url>> {
|
||||||
let commit = commit.to_string();
|
let commit = commit.to_string();
|
||||||
let avatar_url =
|
let avatar_url = self
|
||||||
codeberg::fetch_codeberg_commit_author(repo_owner, repo, &commit, &http_client)
|
.fetch_codeberg_commit_author(repo_owner, repo, &commit, &http_client)
|
||||||
.await?
|
.await?
|
||||||
.map(|author| Url::parse(&author.avatar_url))
|
.map(|author| Url::parse(&author.avatar_url))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(avatar_url)
|
Ok(avatar_url)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Gitee;
|
pub struct Gitee;
|
||||||
|
|
@ -1,13 +1,16 @@
|
|||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use futures::AsyncReadExt;
|
||||||
|
use isahc::config::Configurable;
|
||||||
|
use isahc::{AsyncBody, Request};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::github;
|
|
||||||
use util::http::HttpClient;
|
use util::http::HttpClient;
|
||||||
|
|
||||||
use crate::{
|
use git::{
|
||||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
||||||
PullRequest,
|
PullRequest,
|
||||||
};
|
};
|
||||||
@ -18,8 +21,72 @@ fn pull_request_number_regex() -> &'static Regex {
|
|||||||
PULL_REQUEST_NUMBER_REGEX.get_or_init(|| Regex::new(r"\(#(\d+)\)$").unwrap())
|
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;
|
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]
|
#[async_trait]
|
||||||
impl GitHostingProvider for Github {
|
impl GitHostingProvider for Github {
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
@ -110,15 +177,15 @@ impl GitHostingProvider for Github {
|
|||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
) -> Result<Option<Url>> {
|
) -> Result<Option<Url>> {
|
||||||
let commit = commit.to_string();
|
let commit = commit.to_string();
|
||||||
let avatar_url =
|
let avatar_url = self
|
||||||
github::fetch_github_commit_author(repo_owner, repo, &commit, &http_client)
|
.fetch_github_commit_author(repo_owner, repo, &commit, &http_client)
|
||||||
.await?
|
.await?
|
||||||
.map(|author| -> Result<Url, url::ParseError> {
|
.map(|author| -> Result<Url, url::ParseError> {
|
||||||
let mut url = Url::parse(&author.avatar_url)?;
|
let mut url = Url::parse(&author.avatar_url)?;
|
||||||
url.set_query(Some("size=128"));
|
url.set_query(Some("size=128"));
|
||||||
Ok(url)
|
Ok(url)
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(avatar_url)
|
Ok(avatar_url)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Gitlab;
|
pub struct Gitlab;
|
||||||
|
|
@ -1,8 +1,6 @@
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use git::{BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote};
|
||||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Sourcehut;
|
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 anyhow::{anyhow, bail, Context, Result};
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
use isahc::{config::Configurable, AsyncBody, Request};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -27,75 +26,6 @@ pub struct GithubReleaseAsset {
|
|||||||
pub browser_download_url: String,
|
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(
|
pub async fn latest_github_release(
|
||||||
repo_name_with_owner: &str,
|
repo_name_with_owner: &str,
|
||||||
require_assets: bool,
|
require_assets: bool,
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
pub mod arc_cow;
|
pub mod arc_cow;
|
||||||
pub mod codeberg;
|
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
mod git_author;
|
|
||||||
pub mod github;
|
pub mod github;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
|
@ -46,6 +46,8 @@ file_icons.workspace = true
|
|||||||
file_finder.workspace = true
|
file_finder.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
git.workspace = true
|
||||||
|
git_hosting_providers.workspace = true
|
||||||
go_to_line.workspace = true
|
go_to_line.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
headless.workspace = true
|
headless.workspace = true
|
||||||
|
@ -16,6 +16,7 @@ use editor::Editor;
|
|||||||
use env_logger::Builder;
|
use env_logger::Builder;
|
||||||
use fs::RealFs;
|
use fs::RealFs;
|
||||||
use futures::{future, StreamExt};
|
use futures::{future, StreamExt};
|
||||||
|
use git::GitHostingProviderRegistry;
|
||||||
use gpui::{App, AppContext, AsyncAppContext, Context, Task, VisualContext};
|
use gpui::{App, AppContext, AsyncAppContext, Context, Task, VisualContext};
|
||||||
use image_viewer;
|
use image_viewer;
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
@ -119,6 +120,7 @@ fn init_headless(dev_server_token: DevServerToken) {
|
|||||||
project::Project::init(&client, cx);
|
project::Project::init(&client, cx);
|
||||||
client::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") {
|
let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") {
|
||||||
cx.path_for_auxiliary_executable("git")
|
cx.path_for_auxiliary_executable("git")
|
||||||
.context("could not find git binary path")
|
.context("could not find git binary path")
|
||||||
@ -126,7 +128,9 @@ fn init_headless(dev_server_token: DevServerToken) {
|
|||||||
} else {
|
} else {
|
||||||
None
|
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 =
|
let mut languages =
|
||||||
LanguageRegistry::new(Task::ready(()), cx.background_executor().clone());
|
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();
|
let session_id = Uuid::new_v4().to_string();
|
||||||
reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
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") {
|
let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") {
|
||||||
app.path_for_auxiliary_executable("git")
|
app.path_for_auxiliary_executable("git")
|
||||||
.context("could not find git binary path")
|
.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);
|
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(
|
let user_settings_file_rx = watch_config_file(
|
||||||
&app.background_executor(),
|
&app.background_executor(),
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
@ -236,6 +244,9 @@ fn init_ui(args: Args) {
|
|||||||
AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
|
AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
|
||||||
|
git_hosting_providers::init(cx);
|
||||||
|
|
||||||
SystemAppearance::init(cx);
|
SystemAppearance::init(cx);
|
||||||
OpenListener::set_global(listener.clone(), cx);
|
OpenListener::set_global(listener.clone(), cx);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user