diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/ngit/sub_commands/init.rs | 4 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 4 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/send.rs | 4 | ||||
| -rw-r--r-- | src/lib/repo_ref.rs | 244 |
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 | |||
| 12 | use nostr_sdk::{Kind, NostrSigner, RelayUrl, Timestamp}; | 12 | use nostr_sdk::{Kind, NostrSigner, RelayUrl, Timestamp}; |
| 13 | use serde::{Deserialize, Serialize}; | 13 | use serde::{Deserialize, Serialize}; |
| 14 | 14 | ||
| 15 | #[cfg(not(test))] | ||
| 16 | use crate::client::Client; | ||
| 17 | use crate::{ | 15 | use 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 | ||
| 244 | pub async fn get_repo_coordinates( | 243 | pub 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 | ||
| 252 | pub async fn try_and_get_repo_coordinates( | 251 | pub 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 | ||
| 282 | fn get_repo_coordinates_from_git_config(git_repo: &Repo) -> Result<HashSet<Coordinate>> { | 295 | async 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 | ||
| 294 | fn get_repo_coordinates_from_nostr_remotes(git_repo: &Repo) -> Result<HashSet<Coordinate>> { | 313 | fn 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 | |||
| 322 | fn 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 | ||
| 306 | async fn get_repo_coordinates_from_maintainers_yaml( | 334 | async 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 | ||
| 391 | fn get_repo_coordinates_from_user_prompt(git_repo: &Repo) -> Result<HashSet<Coordinate>> { | 357 | fn 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 | ||
| 404 | fn ask_for_naddr() -> Result<Coordinate> { | 369 | fn 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 | ||