upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bin/ngit
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-20 22:41:50 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-20 22:41:50 +0000
commit68779f91f051822270f156a4185350fb3c4b5017 (patch)
tree2547eaf7cc74bddfb152baa4511c6a46b534d1a1 /src/bin/ngit
parent8f1a1743bd4e85e922ec0cc1f050911a28af4cf0 (diff)
improve `ngit repo` output formatting
- suppress fetch summary (no updates / updates: X) - write blank line to stderr after relay errors for clear separation - show identifier below title only when it differs from name - show earliest unique commit (root_commit) in metadata - restructure infrastructure into grasp servers / additional git servers / additional relays sections - display grasp servers by domain only (strip scheme, npub, repo path) - strip wss:// prefix from relay display - show maintainer names from metadata cache; fall back to short npub - append (you) next to the current user's name wherever it appears - show [name] attribution and the maintainer model note only when there is more than one maintainer
Diffstat (limited to 'src/bin/ngit')
-rw-r--r--src/bin/ngit/sub_commands/repo/mod.rs423
1 files changed, 264 insertions, 159 deletions
diff --git a/src/bin/ngit/sub_commands/repo/mod.rs b/src/bin/ngit/sub_commands/repo/mod.rs
index 62fe766..7914e1d 100644
--- a/src/bin/ngit/sub_commands/repo/mod.rs
+++ b/src/bin/ngit/sub_commands/repo/mod.rs
@@ -1,17 +1,24 @@
1pub mod accept; 1pub mod accept;
2 2
3use std::path::Path;
4
3use anyhow::{Context, Result}; 5use anyhow::{Context, Result};
6use console::Style;
4use ngit::{ 7use ngit::{
5 client::{Params, fetching_with_report, get_repo_ref_from_cache}, 8 client::{Params, fetching_quietly, get_repo_ref_from_cache},
6 repo_ref::{RepoRef, extract_npub, is_grasp_server_clone_url}, 9 login::{existing::load_existing_login, user::get_user_ref_from_cache},
10 repo_ref::{
11 RepoRef, extract_npub, format_grasp_server_url_as_relay_url, is_grasp_server_clone_url,
12 normalize_grasp_server_url,
13 },
14 utils::get_short_git_server_name,
7}; 15};
8use nostr::{PublicKey, TagStandard, ToBech32, nips::nip19::Nip19Coordinate}; 16use nostr::{FromBech32, PublicKey, TagStandard, ToBech32, nips::nip19::Nip19Coordinate};
9 17
10use crate::{ 18use crate::{
11 cli::{Cli, RepoCommands, extract_signer_cli_arguments}, 19 cli::{Cli, RepoCommands, extract_signer_cli_arguments},
12 client::{Client, Connect}, 20 client::{Client, Connect},
13 git::{Repo, RepoActions}, 21 git::{Repo, RepoActions},
14 login,
15 repo_ref::try_and_get_repo_coordinates_when_remote_unknown, 22 repo_ref::try_and_get_repo_coordinates_when_remote_unknown,
16 sub_commands::init, 23 sub_commands::init,
17}; 24};
@@ -35,14 +42,20 @@ async fn show_info(cli_args: &Cli) -> Result<()> {
35 let git_repo_path = git_repo.get_path()?; 42 let git_repo_path = git_repo.get_path()?;
36 let client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo))); 43 let client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo)));
37 44
38 let (_, user_ref, _) = login::login_or_signup( 45 // Attempt a silent login — don't prompt if not logged in.
46 let my_pubkey: Option<PublicKey> = load_existing_login(
39 &Some(&git_repo), 47 &Some(&git_repo),
40 &extract_signer_cli_arguments(cli_args).unwrap_or(None), 48 &extract_signer_cli_arguments(cli_args).unwrap_or(None),
41 &cli_args.password, 49 &cli_args.password,
50 &None,
42 Some(&client), 51 Some(&client),
43 false, 52 true, // silent
53 false, // don't prompt for password
54 false, // don't fetch profile updates
44 ) 55 )
45 .await?; 56 .await
57 .ok()
58 .map(|(_, user_ref, _)| user_ref.public_key);
46 59
47 let repo_coordinate = (try_and_get_repo_coordinates_when_remote_unknown(&git_repo).await).ok(); 60 let repo_coordinate = (try_and_get_repo_coordinates_when_remote_unknown(&git_repo).await).ok();
48 61
@@ -53,8 +66,10 @@ async fn show_info(cli_args: &Cli) -> Result<()> {
53 return Ok(()); 66 return Ok(());
54 }; 67 };
55 68
56 // Fetch latest data from relays 69 // Fetch latest data from relays — suppress the summary line.
57 fetching_with_report(git_repo_path, &client, &repo_coordinate).await?; 70 // fetching_quietly writes a blank line to stderr after errors so there
71 // is clear separation before the repo info below.
72 let _ = fetching_quietly(git_repo_path, &client, &repo_coordinate).await;
58 73
59 let Some(repo_ref) = 74 let Some(repo_ref) =
60 (get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinate).await).ok() 75 (get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinate).await).ok()
@@ -69,34 +84,67 @@ async fn show_info(cli_args: &Cli) -> Result<()> {
69 return Ok(()); 84 return Ok(());
70 }; 85 };
71 86
72 print_repo_info(&repo_ref, &user_ref.public_key, &repo_coordinate); 87 print_repo_info(&repo_ref, my_pubkey.as_ref(), &repo_coordinate, git_repo_path).await;
73 Ok(()) 88 Ok(())
74} 89}
75 90
76#[allow(clippy::too_many_lines)] 91#[allow(clippy::too_many_lines)]
77fn print_repo_info(repo_ref: &RepoRef, my_pubkey: &PublicKey, coordinate: &Nip19Coordinate) { 92async fn print_repo_info(
93 repo_ref: &RepoRef,
94 my_pubkey: Option<&PublicKey>,
95 coordinate: &Nip19Coordinate,
96 git_repo_path: &Path,
97) {
98 let heading = Style::new().bold();
99 let dim = Style::new().dim();
100
101 let multi_maintainer = repo_ref.maintainers.len() > 1
102 || repo_ref
103 .maintainers_without_annoucnement
104 .as_ref()
105 .is_some_and(|v| !v.is_empty());
106
78 // --- Basic metadata --- 107 // --- Basic metadata ---
79 println!("Repository: {}", repo_ref.name); 108 println!("{}", heading.apply_to(&repo_ref.name));
109
110 // Show identifier only when it differs from the name
111 let identifier_slug = repo_ref.identifier.to_lowercase().replace(' ', "-");
112 let name_slug = repo_ref.name.to_lowercase().replace(' ', "-");
113 if identifier_slug != name_slug {
114 println!(
115 "{}",
116 dim.apply_to(format!("identifier: {}", repo_ref.identifier))
117 );
118 }
119
80 if !repo_ref.description.is_empty() { 120 if !repo_ref.description.is_empty() {
81 println!("Description: {}", repo_ref.description); 121 println!("{}", repo_ref.description);
82 } 122 }
83 if !repo_ref.web.is_empty() { 123 if !repo_ref.web.is_empty() {
84 for url in &repo_ref.web { 124 for url in &repo_ref.web {
85 println!("Web: {url}"); 125 println!("{}", dim.apply_to(url));
86 } 126 }
87 } 127 }
88 if !repo_ref.hashtags.is_empty() { 128 if !repo_ref.hashtags.is_empty() {
89 println!("Hashtags: {}", repo_ref.hashtags.join(", ")); 129 println!("{}", dim.apply_to(repo_ref.hashtags.join(" ")));
130 }
131 if !repo_ref.root_commit.is_empty() {
132 println!(
133 "{}",
134 dim.apply_to(format!(
135 "earliest unique commit: {}",
136 &repo_ref.root_commit[..7.min(repo_ref.root_commit.len())]
137 ))
138 );
90 } 139 }
91 println!(); 140 println!();
92 141
93 // --- Maintainers --- 142 // --- Maintainers ---
143 println!("{}", heading.apply_to("Maintainers"));
94 let trusted = &repo_ref.trusted_maintainer; 144 let trusted = &repo_ref.trusted_maintainer;
95 let trusted_npub = trusted.to_bech32().unwrap_or_else(|_| trusted.to_hex()); 145 let trusted_name = display_name_for(trusted, my_pubkey, git_repo_path).await;
96 println!("Trusted maintainer: {}", short_npub(&trusted_npub)); 146 println!(" trusted: {trusted_name}");
97 147
98 // Build a map: pubkey → who listed them (for recursive display)
99 // We walk the events map to find each maintainer's "lister"
100 let co_maintainers: Vec<&PublicKey> = repo_ref 148 let co_maintainers: Vec<&PublicKey> = repo_ref
101 .maintainers 149 .maintainers
102 .iter() 150 .iter()
@@ -104,113 +152,213 @@ fn print_repo_info(repo_ref: &RepoRef, my_pubkey: &PublicKey, coordinate: &Nip19
104 .collect(); 152 .collect();
105 153
106 if !co_maintainers.is_empty() { 154 if !co_maintainers.is_empty() {
107 // For each co-maintainer, find who listed them by inspecting events 155 let mut direct_names: Vec<String> = Vec::new();
108 let mut listed_by: Vec<(String, Option<String>)> = Vec::new(); 156 let mut indirect: Vec<(String, String)> = Vec::new(); // (name, lister_name)
157
109 for co in &co_maintainers { 158 for co in &co_maintainers {
110 let co_npub = co.to_bech32().unwrap_or_else(|_| co.to_hex()); 159 let co_name = display_name_for(co, my_pubkey, git_repo_path).await;
111 // Find which maintainer's event lists this co-maintainer 160 match find_lister(repo_ref, co, trusted) {
112 let lister = find_lister(repo_ref, co, trusted); 161 None => direct_names.push(co_name),
113 listed_by.push((co_npub, lister)); 162 Some(lister_hex) => {
163 let lister_name = if let Ok(pk) = PublicKey::from_hex(&lister_hex) {
164 display_name_for(&pk, my_pubkey, git_repo_path).await
165 } else {
166 short_npub(&lister_hex)
167 };
168 indirect.push((co_name, lister_name));
169 }
170 }
114 } 171 }
115 172
116 // Print directly-listed co-maintainers first, then indirectly-listed 173 if !direct_names.is_empty() {
117 let direct: Vec<_> = listed_by 174 println!(" co-maintainers: {}", direct_names.join(", "));
118 .iter()
119 .filter(|(_, lister)| lister.is_none())
120 .collect();
121 let indirect: Vec<_> = listed_by
122 .iter()
123 .filter(|(_, lister)| lister.is_some())
124 .collect();
125
126 if !direct.is_empty() {
127 let names: Vec<String> = direct.iter().map(|(npub, _)| short_npub(npub)).collect();
128 println!("Co-maintainers: {}", names.join(", "));
129 } 175 }
130 for (npub, lister) in &indirect { 176 for (name, lister_name) in &indirect {
131 if let Some(lister_npub) = lister { 177 println!(
132 println!( 178 " {} {}",
133 " └─ {} is listed by {}, not directly by the trusted maintainer", 179 name,
134 short_npub(npub), 180 dim.apply_to(format!(
135 short_npub(lister_npub) 181 "(listed by {lister_name}, not directly by trusted maintainer)"
136 ); 182 ))
137 } 183 );
138 } 184 }
139 } 185 }
140 186
141 // Maintainers without announcements
142 if let Some(without) = &repo_ref.maintainers_without_annoucnement { 187 if let Some(without) = &repo_ref.maintainers_without_annoucnement {
143 if !without.is_empty() { 188 if !without.is_empty() {
144 let names: Vec<String> = without 189 let mut names = Vec::new();
145 .iter() 190 for pk in without {
146 .map(|pk| { 191 names.push(display_name_for(pk, my_pubkey, git_repo_path).await);
147 let npub = pk.to_bech32().unwrap_or_else(|_| pk.to_hex()); 192 }
148 short_npub(&npub) 193 println!(
149 }) 194 " {}",
150 .collect(); 195 dim.apply_to(format!(
151 println!(" (invited, no announcement yet: {})", names.join(", ")); 196 "invited, no announcement yet: {}",
197 names.join(", ")
198 ))
199 );
152 } 200 }
153 } 201 }
202 println!();
203
204 // --- Infrastructure ---
205 // Split into three groups:
206 // 1. Grasp servers (each bundles a git server + relay)
207 // 2. Additional git servers (non-grasp)
208 // 3. Additional relays (not covered by a grasp server)
209
210 // Relay URLs that grasp servers already cover (for deduplication)
211 let grasp_relay_urls: Vec<String> = repo_ref
212 .git_server
213 .iter()
214 .filter(|s| is_grasp_server_clone_url(s))
215 .filter_map(|s| format_grasp_server_url_as_relay_url(s).ok())
216 .collect();
217
218 let grasp_servers: Vec<&String> = repo_ref
219 .git_server
220 .iter()
221 .filter(|s| is_grasp_server_clone_url(s))
222 .collect();
223
224 let extra_git_servers: Vec<&String> = repo_ref
225 .git_server
226 .iter()
227 .filter(|s| !is_grasp_server_clone_url(s))
228 .collect();
229
230 let extra_relays: Vec<_> = repo_ref
231 .relays
232 .iter()
233 .filter(|r| {
234 let r_str = r.as_str().trim_end_matches('/');
235 !grasp_relay_urls
236 .iter()
237 .any(|g| g.trim_end_matches('/') == r_str)
238 })
239 .collect();
154 240
155 // --- My status --- 241 if !grasp_servers.is_empty() {
156 let my_status = if my_pubkey == trusted { 242 println!("{}", heading.apply_to("Grasp servers"));
157 let has_announcement = repo_ref 243 for server in &grasp_servers {
158 .events 244 // Display just the domain (strip scheme, npub path, and repo path)
159 .keys() 245 let short = normalize_grasp_server_url(server)
160 .any(|c| c.coordinate.public_key == *my_pubkey); 246 .unwrap_or_else(|_| get_short_git_server_name(server));
161 if has_announcement { 247
162 "trusted maintainer [announcement published ✓]" 248 if multi_maintainer {
163 } else { 249 // Owner is encoded in the URL path (the npub)
164 "trusted maintainer [no announcement — run `ngit repo init`]" 250 let owner_label = if let Ok(npub) = extract_npub(server) {
251 if let Ok(pk) = PublicKey::from_bech32(npub) {
252 let name = display_name_for(&pk, my_pubkey, git_repo_path).await;
253 format!("[{name}]")
254 } else {
255 format!("[{}]", short_npub(npub))
256 }
257 } else {
258 String::new()
259 };
260 if owner_label.is_empty() {
261 println!(" {short}");
262 } else {
263 println!(" {short} {}", dim.apply_to(&owner_label));
264 }
265 } else {
266 println!(" {short}");
267 }
165 } 268 }
166 } else if repo_ref.maintainers.contains(my_pubkey) { 269 println!();
167 let has_announcement = repo_ref 270 }
168 .events 271
169 .keys() 272 if !extra_git_servers.is_empty() {
170 .any(|c| c.coordinate.public_key == *my_pubkey); 273 println!("{}", heading.apply_to("Additional git servers"));
171 if has_announcement { 274 for server in &extra_git_servers {
172 "co-maintainer [announcement published ✓]" 275 let short = get_short_git_server_name(server);
173 } else { 276 if multi_maintainer {
174 "co-maintainer [no announcement — run `ngit repo accept`]" 277 let owners = find_server_owners(repo_ref, server, coordinate, my_pubkey, git_repo_path).await;
278 if owners.is_empty() {
279 println!(" {short}");
280 } else {
281 println!(
282 " {short} {}",
283 dim.apply_to(format!("[{}]", owners.join(", ")))
284 );
285 }
286 } else {
287 println!(" {short}");
288 }
175 } 289 }
176 } else { 290 println!();
177 "not a maintainer" 291 }
178 };
179 println!("Your status: {my_status}");
180 println!();
181 292
182 // --- Infrastructure (with per-maintainer attribution) --- 293 if !extra_relays.is_empty() {
183 println!("Git servers (union across all maintainers — any maintainer can add a mirror):"); 294 println!("{}", heading.apply_to("Additional relays"));
184 for server in &repo_ref.git_server { 295 for relay in &extra_relays {
185 let attribution = attribute_server_to_maintainer(repo_ref, server, coordinate); 296 // Strip the wss:// / ws:// prefix for display
186 println!(" {server} {attribution}"); 297 let display = relay
298 .as_str()
299 .trim_start_matches("wss://")
300 .trim_start_matches("ws://")
301 .trim_end_matches('/');
302 if multi_maintainer {
303 let owners =
304 find_relay_owners(repo_ref, relay.as_str(), coordinate, my_pubkey, git_repo_path).await;
305 if owners.is_empty() {
306 println!(" {display}");
307 } else {
308 println!(
309 " {display} {}",
310 dim.apply_to(format!("[{}]", owners.join(", ")))
311 );
312 }
313 } else {
314 println!(" {display}");
315 }
316 }
317 println!();
187 } 318 }
188 println!();
189 319
190 println!("Relays (union across all maintainers — any maintainer can add a relay):"); 320 // --- Maintainer model note (only relevant when there are multiple maintainers) ---
191 for relay in &repo_ref.relays { 321 if multi_maintainer {
192 let attribution = attribute_relay_to_maintainer(repo_ref, relay.as_str(), coordinate); 322 println!(
193 println!(" {relay} {attribution}"); 323 "{}",
324 dim.apply_to(
325 "Note: git servers and relays are pooled from all maintainers' announcements.\n\
326 Name, description, web, and hashtags come from the most recently updated announcement.\n\
327 Each maintainer independently decides who they list as co-maintainers;\n\
328 if Alice lists Bob and Bob lists Carol, all three are in the maintainer set."
329 )
330 );
194 } 331 }
195 println!(); 332}
196 333
197 // --- Maintainer model note --- 334/// Resolve a display name for a public key from the local metadata cache.
198 println!("Note: git servers and relays are pooled from all maintainers' announcements."); 335/// Appends " (you)" when `pk` matches `my_pubkey`.
199 println!( 336/// Falls back to a short npub if no metadata is cached.
200 " Name, description, web, and hashtags come from the most recently updated announcement." 337async fn display_name_for(
201 ); 338 pk: &PublicKey,
202 println!(" Each maintainer independently decides who they list as co-maintainers;"); 339 my_pubkey: Option<&PublicKey>,
203 println!(" if Alice lists Bob and Bob lists Carol, all three are in the maintainer set."); 340 git_repo_path: &Path,
341) -> String {
342 let name = if let Ok(user_ref) = get_user_ref_from_cache(Some(git_repo_path), pk).await {
343 user_ref.metadata.name
344 } else {
345 let npub = pk.to_bech32().unwrap_or_else(|_| pk.to_hex());
346 short_npub(&npub)
347 };
348 if my_pubkey == Some(pk) {
349 format!("{name} (you)")
350 } else {
351 name
352 }
204} 353}
205 354
206/// Find which maintainer's event lists `target` as a maintainer. 355/// Find which maintainer's event lists `target` as a maintainer.
207/// Returns `None` if listed directly by the trusted maintainer, 356/// Returns `None` if listed directly by the trusted maintainer,
208/// or `Some(lister_npub)` if listed by a co-maintainer. 357/// or `Some(lister_pubkey_hex)` if listed by a co-maintainer.
209fn find_lister(repo_ref: &RepoRef, target: &PublicKey, trusted: &PublicKey) -> Option<String> { 358fn find_lister(repo_ref: &RepoRef, target: &PublicKey, trusted: &PublicKey) -> Option<String> {
210 use nostr::nips::nip01::Coordinate; 359 use nostr::nips::nip01::Coordinate;
211 use nostr_sdk::Kind; 360 use nostr_sdk::Kind;
212 361
213 // Check if the trusted maintainer's event lists this target directly
214 let trusted_coord = nostr::nips::nip19::Nip19Coordinate { 362 let trusted_coord = nostr::nips::nip19::Nip19Coordinate {
215 coordinate: Coordinate { 363 coordinate: Coordinate {
216 kind: Kind::GitRepoAnnouncement, 364 kind: Kind::GitRepoAnnouncement,
@@ -220,8 +368,7 @@ fn find_lister(repo_ref: &RepoRef, target: &PublicKey, trusted: &PublicKey) -> O
220 relays: vec![], 368 relays: vec![],
221 }; 369 };
222 if let Some(event) = repo_ref.events.get(&trusted_coord) { 370 if let Some(event) = repo_ref.events.get(&trusted_coord) {
223 // Parse the event's maintainers tag 371 let listed: Vec<PublicKey> = event
224 let listed_in_trusted: Vec<PublicKey> = event
225 .tags 372 .tags
226 .iter() 373 .iter()
227 .filter_map(|t| { 374 .filter_map(|t| {
@@ -232,18 +379,17 @@ fn find_lister(repo_ref: &RepoRef, target: &PublicKey, trusted: &PublicKey) -> O
232 } 379 }
233 }) 380 })
234 .collect(); 381 .collect();
235 if listed_in_trusted.contains(target) { 382 if listed.contains(target) {
236 return None; // directly listed by trusted maintainer 383 return None;
237 } 384 }
238 } 385 }
239 386
240 // Otherwise find which co-maintainer lists them
241 for (coord, event) in &repo_ref.events { 387 for (coord, event) in &repo_ref.events {
242 if coord.coordinate.public_key == *trusted { 388 if coord.coordinate.public_key == *trusted {
243 continue; 389 continue;
244 } 390 }
245 let lister = coord.coordinate.public_key; 391 let lister = coord.coordinate.public_key;
246 let maintainers_listed: Vec<PublicKey> = event 392 let lister_listed: Vec<PublicKey> = event
247 .tags 393 .tags
248 .iter() 394 .iter()
249 .filter_map(|t| { 395 .filter_map(|t| {
@@ -254,55 +400,20 @@ fn find_lister(repo_ref: &RepoRef, target: &PublicKey, trusted: &PublicKey) -> O
254 } 400 }
255 }) 401 })
256 .collect(); 402 .collect();
257 if maintainers_listed.contains(target) { 403 if lister_listed.contains(target) {
258 let lister_npub = lister.to_bech32().unwrap_or_else(|_| lister.to_hex()); 404 return Some(lister.to_hex());
259 return Some(lister_npub);
260 } 405 }
261 } 406 }
262 407
263 None 408 None
264} 409}
265 410
266/// Find which maintainer(s) contribute a given git server URL. 411async fn find_server_owners(
267fn attribute_server_to_maintainer(
268 repo_ref: &RepoRef,
269 server_url: &str,
270 coordinate: &Nip19Coordinate,
271) -> String {
272 // For grasp-format URLs, the npub in the path tells us the owner
273 if is_grasp_server_clone_url(server_url) {
274 if let Ok(npub) = extract_npub(server_url) {
275 return format!("[{}]", short_npub(npub));
276 }
277 }
278
279 // For non-grasp URLs, find which maintainer's event lists it
280 let owners = find_server_owners(repo_ref, server_url, coordinate);
281 if owners.is_empty() {
282 String::new()
283 } else {
284 format!("[{}]", owners.join(", "))
285 }
286}
287
288/// Find which maintainer(s) contribute a given relay URL.
289fn attribute_relay_to_maintainer(
290 repo_ref: &RepoRef,
291 relay_url: &str,
292 coordinate: &Nip19Coordinate,
293) -> String {
294 let owners = find_relay_owners(repo_ref, relay_url, coordinate);
295 if owners.is_empty() {
296 String::new()
297 } else {
298 format!("[{}]", owners.join(", "))
299 }
300}
301
302fn find_server_owners(
303 repo_ref: &RepoRef, 412 repo_ref: &RepoRef,
304 server_url: &str, 413 server_url: &str,
305 _coordinate: &Nip19Coordinate, 414 _coordinate: &Nip19Coordinate,
415 my_pubkey: Option<&PublicKey>,
416 git_repo_path: &Path,
306) -> Vec<String> { 417) -> Vec<String> {
307 let mut owners = Vec::new(); 418 let mut owners = Vec::new();
308 for (coord, event) in &repo_ref.events { 419 for (coord, event) in &repo_ref.events {
@@ -312,22 +423,20 @@ fn find_server_owners(
312 .iter() 423 .iter()
313 .any(|s| s.trim_end_matches('/') == server_url.trim_end_matches('/')) 424 .any(|s| s.trim_end_matches('/') == server_url.trim_end_matches('/'))
314 { 425 {
315 let npub = coord 426 let pk = coord.coordinate.public_key;
316 .coordinate 427 owners.push(display_name_for(&pk, my_pubkey, git_repo_path).await);
317 .public_key
318 .to_bech32()
319 .unwrap_or_else(|_| coord.coordinate.public_key.to_hex());
320 owners.push(short_npub(&npub));
321 } 428 }
322 } 429 }
323 } 430 }
324 owners 431 owners
325} 432}
326 433
327fn find_relay_owners( 434async fn find_relay_owners(
328 repo_ref: &RepoRef, 435 repo_ref: &RepoRef,
329 relay_url: &str, 436 relay_url: &str,
330 _coordinate: &Nip19Coordinate, 437 _coordinate: &Nip19Coordinate,
438 my_pubkey: Option<&PublicKey>,
439 git_repo_path: &Path,
331) -> Vec<String> { 440) -> Vec<String> {
332 let mut owners = Vec::new(); 441 let mut owners = Vec::new();
333 for (coord, event) in &repo_ref.events { 442 for (coord, event) in &repo_ref.events {
@@ -337,19 +446,15 @@ fn find_relay_owners(
337 .iter() 446 .iter()
338 .any(|r| r.as_str().trim_end_matches('/') == relay_url.trim_end_matches('/')) 447 .any(|r| r.as_str().trim_end_matches('/') == relay_url.trim_end_matches('/'))
339 { 448 {
340 let npub = coord 449 let pk = coord.coordinate.public_key;
341 .coordinate 450 owners.push(display_name_for(&pk, my_pubkey, git_repo_path).await);
342 .public_key
343 .to_bech32()
344 .unwrap_or_else(|_| coord.coordinate.public_key.to_hex());
345 owners.push(short_npub(&npub));
346 } 451 }
347 } 452 }
348 } 453 }
349 owners 454 owners
350} 455}
351 456
352/// Shorten an npub for display: show first 8 + "..." + last 4 chars. 457/// Shorten an npub for display: show first 12 + "..." + last 4 chars.
353fn short_npub(npub: &str) -> String { 458fn short_npub(npub: &str) -> String {
354 if npub.len() <= 16 { 459 if npub.len() <= 16 {
355 return npub.to_string(); 460 return npub.to_string();