Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:32:48.001Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

hosting_provider.rs

256 lines · 7.4 KB · rust
1use std::{ops::Range, sync::Arc};
2
3use anyhow::Result;
4use async_trait::async_trait;
5use derive_more::{Deref, DerefMut};
6use gpui::{App, Global, SharedString};
7use http_client::HttpClient;
8use itertools::Itertools;
9use parking_lot::RwLock;
10use url::Url;
11
12use crate::repository::RepoPath;
13
14#[derive(Debug, PartialEq, Eq, Clone)]
15pub struct PullRequest {
16    pub number: u32,
17    pub url: Url,
18}
19
20#[derive(Clone)]
21pub struct GitRemote {
22    pub host: Arc<dyn GitHostingProvider + Send + Sync + 'static>,
23    pub owner: SharedString,
24    pub repo: SharedString,
25}
26
27impl std::fmt::Debug for GitRemote {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        f.debug_struct("GitRemote")
30            .field("host", &self.host.name())
31            .field("owner", &self.owner)
32            .field("repo", &self.repo)
33            .finish()
34    }
35}
36
37impl GitRemote {
38    pub fn host_supports_avatars(&self) -> bool {
39        self.host.supports_avatars()
40    }
41
42    pub async fn avatar_url(
43        &self,
44        commit: SharedString,
45        author_email: Option<SharedString>,
46        client: Arc<dyn HttpClient>,
47    ) -> Option<Url> {
48        self.host
49            .commit_author_avatar_url(&self.owner, &self.repo, commit, author_email, client)
50            .await
51            .ok()
52            .flatten()
53    }
54}
55
56pub struct BuildCommitPermalinkParams<'a> {
57    pub sha: &'a str,
58}
59
60pub struct BuildPermalinkParams<'a> {
61    pub sha: &'a str,
62    /// URL-escaped path using unescaped `/` as the directory separator.
63    pub path: String,
64    pub selection: Option<Range<u32>>,
65}
66
67impl<'a> BuildPermalinkParams<'a> {
68    pub fn new(sha: &'a str, path: &RepoPath, selection: Option<Range<u32>>) -> Self {
69        Self {
70            sha,
71            path: path.components().map(urlencoding::encode).join("/"),
72            selection,
73        }
74    }
75}
76
77/// A Git hosting provider.
78#[async_trait]
79pub trait GitHostingProvider {
80    /// Returns the name of the provider.
81    fn name(&self) -> String;
82
83    /// Returns the base URL of the provider.
84    fn base_url(&self) -> Url;
85
86    /// Returns a permalink to a Git commit on this hosting provider.
87    fn build_commit_permalink(
88        &self,
89        remote: &ParsedGitRemote,
90        params: BuildCommitPermalinkParams,
91    ) -> Url;
92
93    /// Returns a permalink to a file and/or selection on this hosting provider.
94    fn build_permalink(&self, remote: ParsedGitRemote, params: BuildPermalinkParams) -> Url;
95
96    /// Returns a URL to create a pull request on this hosting provider.
97    fn build_create_pull_request_url(
98        &self,
99        _remote: &ParsedGitRemote,
100        _source_branch: &str,
101    ) -> Option<Url> {
102        None
103    }
104
105    /// Returns whether this provider supports avatars.
106    fn supports_avatars(&self) -> bool;
107
108    /// Returns a URL fragment to the given line selection.
109    fn line_fragment(&self, selection: &Range<u32>) -> String {
110        if selection.start == selection.end {
111            let line = selection.start + 1;
112
113            self.format_line_number(line)
114        } else {
115            let start_line = selection.start + 1;
116            let end_line = selection.end + 1;
117
118            self.format_line_numbers(start_line, end_line)
119        }
120    }
121
122    /// Returns a formatted line number to be placed in a permalink URL.
123    fn format_line_number(&self, line: u32) -> String;
124
125    /// Returns a formatted range of line numbers to be placed in a permalink URL.
126    fn format_line_numbers(&self, start_line: u32, end_line: u32) -> String;
127
128    fn parse_remote_url(&self, url: &str) -> Option<ParsedGitRemote>;
129
130    fn extract_pull_request(
131        &self,
132        _remote: &ParsedGitRemote,
133        _message: &str,
134    ) -> Option<PullRequest> {
135        None
136    }
137
138    async fn commit_author_avatar_url(
139        &self,
140        _repo_owner: &str,
141        _repo: &str,
142        _commit: SharedString,
143        _author_email: Option<SharedString>,
144        _http_client: Arc<dyn HttpClient>,
145    ) -> Result<Option<Url>> {
146        Ok(None)
147    }
148}
149
150#[derive(Default, Deref, DerefMut)]
151struct GlobalGitHostingProviderRegistry(Arc<GitHostingProviderRegistry>);
152
153impl Global for GlobalGitHostingProviderRegistry {}
154
155#[derive(Default)]
156struct GitHostingProviderRegistryState {
157    default_providers: Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
158    setting_providers: Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
159}
160
161#[derive(Default)]
162pub struct GitHostingProviderRegistry {
163    state: RwLock<GitHostingProviderRegistryState>,
164}
165
166impl GitHostingProviderRegistry {
167    /// Returns the global [`GitHostingProviderRegistry`].
168    #[track_caller]
169    pub fn global(cx: &App) -> Arc<Self> {
170        cx.global::<GlobalGitHostingProviderRegistry>().0.clone()
171    }
172
173    /// Returns the global [`GitHostingProviderRegistry`], if one is set.
174    pub fn try_global(cx: &App) -> Option<Arc<Self>> {
175        cx.try_global::<GlobalGitHostingProviderRegistry>()
176            .map(|registry| registry.0.clone())
177    }
178
179    /// Returns the global [`GitHostingProviderRegistry`].
180    ///
181    /// Inserts a default [`GitHostingProviderRegistry`] if one does not yet exist.
182    pub fn default_global(cx: &mut App) -> Arc<Self> {
183        cx.default_global::<GlobalGitHostingProviderRegistry>()
184            .0
185            .clone()
186    }
187
188    /// Sets the global [`GitHostingProviderRegistry`].
189    pub fn set_global(registry: Arc<GitHostingProviderRegistry>, cx: &mut App) {
190        cx.set_global(GlobalGitHostingProviderRegistry(registry));
191    }
192
193    /// Returns a new [`GitHostingProviderRegistry`].
194    pub fn new() -> Self {
195        Self {
196            state: RwLock::new(GitHostingProviderRegistryState {
197                setting_providers: Vec::default(),
198                default_providers: Vec::default(),
199            }),
200        }
201    }
202
203    /// Returns the list of all [`GitHostingProvider`]s in the registry.
204    pub fn list_hosting_providers(
205        &self,
206    ) -> Vec<Arc<dyn GitHostingProvider + Send + Sync + 'static>> {
207        let state = self.state.read();
208        state
209            .default_providers
210            .iter()
211            .cloned()
212            .chain(state.setting_providers.iter().cloned())
213            .collect()
214    }
215
216    pub fn set_setting_providers(
217        &self,
218        providers: impl IntoIterator<Item = Arc<dyn GitHostingProvider + Send + Sync + 'static>>,
219    ) {
220        let mut state = self.state.write();
221        state.setting_providers.clear();
222        state.setting_providers.extend(providers);
223    }
224
225    /// Adds the provided [`GitHostingProvider`] to the registry.
226    pub fn register_hosting_provider(
227        &self,
228        provider: Arc<dyn GitHostingProvider + Send + Sync + 'static>,
229    ) {
230        self.state.write().default_providers.push(provider);
231    }
232}
233
234#[derive(Debug, PartialEq)]
235pub struct ParsedGitRemote {
236    pub owner: Arc<str>,
237    pub repo: Arc<str>,
238}
239
240pub fn parse_git_remote_url(
241    provider_registry: Arc<GitHostingProviderRegistry>,
242    url: &str,
243) -> Option<(
244    Arc<dyn GitHostingProvider + Send + Sync + 'static>,
245    ParsedGitRemote,
246)> {
247    provider_registry
248        .list_hosting_providers()
249        .into_iter()
250        .find_map(|provider| {
251            provider
252                .parse_remote_url(url)
253                .map(|parsed_remote| (provider, parsed_remote))
254        })
255}
256
Served at tenant.openagents/omega Member data and write actions are omitted.