upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-12-04 15:36:17 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-12-04 15:36:17 +0000
commite00f712fb09d2a55fbfd36696f3725bc44c140f5 (patch)
treed937f121f70050bbda40830df56e952cbeb5860c /src
parentd2478dbca6c5d3f61331ceabe20c6d9315cd6840 (diff)
fix: `ngit` load correct repo address
git-remote-nostr is always called with a nostr remote that specifies a single repository coordinate whereas `ngit` commands need to discover this. this change moves from getting this value from the following places in priority order: 1. git config item `nostr.repo` 2. first nostr remote discovered 2. maintainers.yaml 3. prompt the user to: 1. nostr remote - if multiple, prompt the user to select 2. git config item `nostr.repo` 3. maintainers.yaml - removing the legacy format 4. prompt the user - now with support for nostr remote format
Diffstat (limited to 'src')
-rw-r--r--src/bin/ngit/sub_commands/init.rs4
-rw-r--r--src/bin/ngit/sub_commands/list.rs4
-rw-r--r--src/bin/ngit/sub_commands/send.rs4
-rw-r--r--src/lib/repo_ref.rs244
4 files changed, 112 insertions, 144 deletions
diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs
index 9d87ba2..6fc1ec4 100644
--- a/src/bin/ngit/sub_commands/init.rs
+++ b/src/bin/ngit/sub_commands/init.rs
@@ -14,7 +14,7 @@ use crate::{
14 login, 14 login,
15 repo_ref::{ 15 repo_ref::{
16 extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml, 16 extract_pks, get_repo_config_from_yaml, save_repo_config_to_yaml,
17 try_and_get_repo_coordinates, RepoRef, 17 try_and_get_repo_coordinates_when_remote_unknown, RepoRef,
18 }, 18 },
19}; 19};
20 20
@@ -61,7 +61,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
61 let mut client = Client::default(); 61 let mut client = Client::default();
62 62
63 let repo_coordinate = if let Ok(repo_coordinate) = 63 let repo_coordinate = if let Ok(repo_coordinate) =
64 try_and_get_repo_coordinates(&git_repo, &client, false).await 64 try_and_get_repo_coordinates_when_remote_unknown(&git_repo).await
65 { 65 {
66 Some(repo_coordinate) 66 Some(repo_coordinate)
67 } else { 67 } else {
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs
index 60f8e46..f79e284 100644
--- a/src/bin/ngit/sub_commands/list.rs
+++ b/src/bin/ngit/sub_commands/list.rs
@@ -19,7 +19,7 @@ use crate::{
19 commit_msg_from_patch_oneliner, event_is_revision_root, event_to_cover_letter, 19 commit_msg_from_patch_oneliner, event_is_revision_root, event_to_cover_letter,
20 patch_supports_commit_ids, 20 patch_supports_commit_ids,
21 }, 21 },
22 repo_ref::get_repo_coordinates, 22 repo_ref::get_repo_coordinates_when_remote_unknown,
23}; 23};
24 24
25#[allow(clippy::too_many_lines)] 25#[allow(clippy::too_many_lines)]
@@ -33,7 +33,7 @@ pub async fn launch() -> Result<()> {
33 33
34 let client = Client::default(); 34 let client = Client::default();
35 35
36 let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?; 36 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo).await?;
37 37
38 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; 38 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
39 39
diff --git a/src/bin/ngit/sub_commands/send.rs b/src/bin/ngit/sub_commands/send.rs
index 6c9d8eb..bc4d18a 100644
--- a/src/bin/ngit/sub_commands/send.rs
+++ b/src/bin/ngit/sub_commands/send.rs
@@ -20,7 +20,7 @@ use crate::{
20 git::{identify_ahead_behind, Repo, RepoActions}, 20 git::{identify_ahead_behind, Repo, RepoActions},
21 git_events::{event_is_patch_set_root, event_tag_from_nip19_or_hex}, 21 git_events::{event_is_patch_set_root, event_tag_from_nip19_or_hex},
22 login, 22 login,
23 repo_ref::get_repo_coordinates, 23 repo_ref::get_repo_coordinates_when_remote_unknown,
24}; 24};
25 25
26#[derive(Debug, clap::Args)] 26#[derive(Debug, clap::Args)]
@@ -54,7 +54,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re
54 54
55 let mut client = Client::default(); 55 let mut client = Client::default();
56 56
57 let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?; 57 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo).await?;
58 58
59 if !no_fetch { 59 if !no_fetch {
60 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; 60 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs
index 988c87d..8f8b4ad 100644
--- a/src/lib/repo_ref.rs
+++ b/src/lib/repo_ref.rs
@@ -12,12 +12,11 @@ use nostr::{nips::nip01::Coordinate, FromBech32, PublicKey, Tag, TagStandard, To
12use nostr_sdk::{Kind, NostrSigner, RelayUrl, Timestamp}; 12use nostr_sdk::{Kind, NostrSigner, RelayUrl, Timestamp};
13use serde::{Deserialize, Serialize}; 13use serde::{Deserialize, Serialize};
14 14
15#[cfg(not(test))]
16use crate::client::Client;
17use crate::{ 15use crate::{
18 cli_interactor::{Interactor, InteractorPrompt, PromptInputParms}, 16 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptInputParms},
19 client::{get_event_from_global_cache, get_events_from_local_cache, sign_event, Connect}, 17 client::sign_event,
20 git::{nostr_url::NostrUrlDecoded, Repo, RepoActions}, 18 git::{nostr_url::NostrUrlDecoded, Repo, RepoActions},
19 login::user::get_user_details,
21}; 20};
22 21
23#[derive(Clone)] 22#[derive(Clone)]
@@ -241,155 +240,121 @@ impl RepoRef {
241 } 240 }
242} 241}
243 242
244pub async fn get_repo_coordinates( 243pub async fn get_repo_coordinates_when_remote_unknown(git_repo: &Repo) -> Result<Coordinate> {
245 git_repo: &Repo, 244 if let Ok(c) = try_and_get_repo_coordinates_when_remote_unknown(git_repo).await {
246 #[cfg(test)] client: &crate::client::MockConnect, 245 Ok(c)
247 #[cfg(not(test))] client: &Client, 246 } else {
248) -> Result<Coordinate> { 247 get_repo_coordinates_from_user_prompt(git_repo)
249 try_and_get_repo_coordinates(git_repo, client, true).await 248 }
250} 249}
251 250
252pub async fn try_and_get_repo_coordinates( 251pub async fn try_and_get_repo_coordinates_when_remote_unknown(
253 git_repo: &Repo, 252 git_repo: &Repo,
254 #[cfg(test)] client: &crate::client::MockConnect,
255 #[cfg(not(test))] client: &Client,
256 prompt_user: bool,
257) -> Result<Coordinate> { 253) -> Result<Coordinate> {
258 let mut repo_coordinates = get_repo_coordinates_from_git_config(git_repo)?; 254 let remote_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo)?;
259 255 if remote_coordinates.is_empty() {
260 if repo_coordinates.is_empty() { 256 if let Ok(c) = get_repo_coordinates_from_git_config(git_repo) {
261 repo_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo)?; 257 Ok(c)
262 }
263
264 if repo_coordinates.is_empty() {
265 repo_coordinates = get_repo_coordinates_from_maintainers_yaml(git_repo, client).await?;
266 }
267
268 if repo_coordinates.is_empty() {
269 if prompt_user {
270 repo_coordinates = get_repo_coordinates_from_user_prompt(git_repo)?;
271 } else { 258 } else {
272 bail!("couldn't find repo coordinates in git config nostr.repo or in maintainers.yaml"); 259 get_repo_coordinates_from_maintainers_yaml(git_repo)
260 .await
261 // not mentioning maintainers.yaml as its not auto generated anymore
262 .context("no nostr git remotes or git config \"nostr.repo\" value")
273 } 263 }
264 } else if remote_coordinates.len() == 1
265 || remote_coordinates.values().all(|coordinate| {
266 let first = remote_coordinates.values().next().unwrap();
267 coordinate.public_key == first.public_key && coordinate.identifier == first.identifier
268 })
269 {
270 Ok(remote_coordinates.values().next().unwrap().clone())
271 } else {
272 let choice_index = Interactor::default().choice(
273 PromptChoiceParms::default()
274 .with_prompt("select nostr repository from those listed as git remotes")
275 .with_default(0)
276 .with_choices(
277 get_nostr_git_remote_selection_labels(git_repo, &remote_coordinates).await?,
278 ),
279 )?;
280
281 Ok(remote_coordinates
282 .get(
283 remote_coordinates
284 .keys()
285 .cloned()
286 .collect::<Vec<String>>()
287 .get(choice_index)
288 .unwrap(),
289 )
290 .unwrap()
291 .clone())
274 } 292 }
275 Ok(repo_coordinates
276 .iter()
277 .next()
278 .context("would have bailed if no coordinates found")?
279 .clone())
280} 293}
281 294
282fn get_repo_coordinates_from_git_config(git_repo: &Repo) -> Result<HashSet<Coordinate>> { 295async fn get_nostr_git_remote_selection_labels(
283 let mut repo_coordinates = HashSet::new(); 296 git_repo: &Repo,
284 if let Some(repo_override) = git_repo.get_git_config_item("nostr.repo", Some(false))? { 297 remote_coordinates: &HashMap<String, Coordinate>,
285 for s in repo_override.split(',') { 298) -> Result<Vec<String>> {
286 if let Ok(c) = Coordinate::parse(s) { 299 let mut res = vec![];
287 repo_coordinates.insert(c); 300 for (remote, c) in remote_coordinates {
288 } 301 res.push(format!(
289 } 302 "{remote} - {}/{}",
303 get_user_details(&c.public_key, None, Some(git_repo.get_path()?), true)
304 .await?
305 .metadata
306 .name,
307 c.identifier
308 ));
290 } 309 }
291 Ok(repo_coordinates) 310 Ok(res)
292} 311}
293 312
294fn get_repo_coordinates_from_nostr_remotes(git_repo: &Repo) -> Result<HashSet<Coordinate>> { 313fn get_repo_coordinates_from_git_config(git_repo: &Repo) -> Result<Coordinate> {
295 let mut repo_coordinates = HashSet::new(); 314 Coordinate::parse(
315 git_repo
316 .get_git_config_item("nostr.repo", Some(false))?
317 .context("git config item \"nostr.repo\" is not set in local repository")?,
318 )
319 .context("git config item \"nostr.repo\" is not an naddr")
320}
321
322fn get_repo_coordinates_from_nostr_remotes(git_repo: &Repo) -> Result<HashMap<String, Coordinate>> {
323 let mut repo_coordinates = HashMap::new();
296 for remote_name in git_repo.git_repo.remotes()?.iter().flatten() { 324 for remote_name in git_repo.git_repo.remotes()?.iter().flatten() {
297 if let Some(remote_url) = git_repo.git_repo.find_remote(remote_name)?.url() { 325 if let Some(remote_url) = git_repo.git_repo.find_remote(remote_name)?.url() {
298 if let Ok(nostr_url_decoded) = NostrUrlDecoded::from_str(remote_url) { 326 if let Ok(nostr_url_decoded) = NostrUrlDecoded::from_str(remote_url) {
299 repo_coordinates.insert(nostr_url_decoded.coordinate); 327 repo_coordinates.insert(remote_name.to_string(), nostr_url_decoded.coordinate);
300 } 328 }
301 } 329 }
302 } 330 }
303 Ok(repo_coordinates) 331 Ok(repo_coordinates)
304} 332}
305 333
306async fn get_repo_coordinates_from_maintainers_yaml( 334async fn get_repo_coordinates_from_maintainers_yaml(git_repo: &Repo) -> Result<Coordinate> {
307 git_repo: &Repo, 335 let repo_config = get_repo_config_from_yaml(git_repo)?;
308 #[cfg(test)] client: &crate::client::MockConnect, 336
309 #[cfg(not(test))] client: &Client, 337 Ok(Coordinate {
310) -> Result<HashSet<Coordinate>> { 338 identifier: repo_config
311 let mut repo_coordinates = HashSet::new(); 339 .identifier
312 if let Ok(repo_config) = get_repo_config_from_yaml(git_repo) { 340 .context("maintainers.yaml doesnt list the identifier")?,
313 let maintainers = { 341 kind: Kind::GitRepoAnnouncement,
314 let mut maintainers = HashSet::new(); 342 public_key: PublicKey::from_bech32(
315 for m in &repo_config.maintainers { 343 repo_config
316 if let Ok(maintainer) = PublicKey::parse(m) { 344 .maintainers
317 maintainers.insert(maintainer); 345 .first()
318 } 346 .context("maintainers.yaml doesnt list any maintainers")?,
319 } 347 )
320 maintainers 348 .context("maintainers.yaml doesn't list the first maintainer using a valid npub")?,
321 }; 349 relays: repo_config
322 if let Some(identifier) = repo_config.identifier { 350 .relays
323 for public_key in maintainers { 351 .iter()
324 repo_coordinates.insert(Coordinate { 352 .filter_map(|url| RelayUrl::parse(url).ok())
325 kind: Kind::GitRepoAnnouncement, 353 .collect(),
326 public_key, 354 })
327 identifier: identifier.clone(),
328 relays: vec![],
329 });
330 }
331 } else {
332 // if repo_config.identifier.is_empty() {
333 // this will only apply for a few repositories created before ngit v1.3
334 // that haven't updated their maintainers.yaml
335 if let Ok(Some(current_user_npub)) = git_repo.get_git_config_item("nostr.npub", None) {
336 if let Ok(current_user) = PublicKey::parse(current_user_npub) {
337 for m in &repo_config.maintainers {
338 if let Ok(maintainer) = PublicKey::parse(m) {
339 if current_user.eq(&maintainer) {
340 println!(
341 "please run `ngit init` to add the repo identifier to maintainers.yaml"
342 );
343 }
344 }
345 }
346 }
347 }
348 // look find all repo refs with root_commit. for identifier
349 let filter = nostr::Filter::default()
350 .kind(nostr::Kind::GitRepoAnnouncement)
351 .reference(git_repo.get_root_commit()?.to_string())
352 .authors(maintainers.clone());
353 let mut events =
354 get_events_from_local_cache(git_repo.get_path()?, vec![filter.clone()]).await?;
355 if events.is_empty() {
356 events =
357 get_event_from_global_cache(Some(git_repo.get_path()?), vec![filter.clone()])
358 .await?;
359 }
360 if events.is_empty() {
361 println!(
362 "finding repository events for this repository for npubs in maintainers.yaml"
363 );
364 events = client
365 .get_events(client.get_fallback_relays().clone(), vec![filter.clone()])
366 .await?;
367 }
368 if let Some(e) = events.first() {
369 if let Some(identifier) = e.tags.identifier() {
370 for m in &repo_config.maintainers {
371 if let Ok(maintainer) = PublicKey::parse(m) {
372 repo_coordinates.insert(Coordinate {
373 kind: Kind::GitRepoAnnouncement,
374 public_key: maintainer,
375 identifier: identifier.to_string(),
376 relays: vec![],
377 });
378 }
379 }
380 }
381 } else {
382 let c = ask_for_naddr()?;
383 git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?;
384 repo_coordinates.insert(c);
385 }
386 }
387 }
388 Ok(repo_coordinates)
389} 355}
390 356
391fn get_repo_coordinates_from_user_prompt(git_repo: &Repo) -> Result<HashSet<Coordinate>> { 357fn get_repo_coordinates_from_user_prompt(git_repo: &Repo) -> Result<Coordinate> {
392 let mut repo_coordinates = HashSet::new();
393 // TODO: present list of events filter by root_commit 358 // TODO: present list of events filter by root_commit
394 // TODO: fallback to search based on identifier 359 // TODO: fallback to search based on identifier
395 let c = ask_for_naddr()?; 360 let c = ask_for_naddr()?;
@@ -397,25 +362,28 @@ fn get_repo_coordinates_from_user_prompt(git_repo: &Repo) -> Result<HashSet<Coor
397 // means next time the user won't be prompted and may not know how to 362 // means next time the user won't be prompted and may not know how to
398 // change the selected repo 363 // change the selected repo
399 git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?; 364 git_repo.save_git_config_item("nostr.repo", &c.to_bech32()?, false)?;
400 repo_coordinates.insert(c); 365 // TODO: prompt to add a git remote
401 Ok(repo_coordinates) 366 Ok(c)
402} 367}
403 368
404fn ask_for_naddr() -> Result<Coordinate> { 369fn ask_for_naddr() -> Result<Coordinate> {
405 let dim = Style::new().color256(247); 370 let dim = Style::new().color256(247);
406 println!( 371 println!(
407 "{}", 372 "{}",
408 dim.apply_to("hint: https://gitworkshop.dev/repos lists repositories and their naddr"), 373 dim.apply_to(
374 "hint: https://gitworkshop.dev/repos lists repositories and their nostr addresses"
375 ),
409 ); 376 );
410 377
411 Ok(loop { 378 Ok(loop {
412 if let Ok(c) = Coordinate::parse( 379 let input = Interactor::default()
413 Interactor::default() 380 .input(PromptInputParms::default().with_prompt("nostr repository"))?;
414 .input(PromptInputParms::default().with_prompt("repository naddr"))?, 381 if let Ok(c) = Coordinate::parse(&input) {
415 ) {
416 break c; 382 break c;
383 } else if let Ok(nostr_url) = NostrUrlDecoded::from_str(&input) {
384 break nostr_url.coordinate;
417 } 385 }
418 println!("not a valid naddr"); 386 println!("not a valid naddr or git nostr remote URL starting nostr://");
419 }) 387 })
420} 388}
421 389