diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/client.rs | 164 | ||||
| -rw-r--r-- | src/lib/git/mod.rs | 20 | ||||
| -rw-r--r-- | src/lib/git_events.rs | 321 | ||||
| -rw-r--r-- | src/lib/login/fresh.rs | 4 | ||||
| -rw-r--r-- | src/lib/repo_ref.rs | 23 |
5 files changed, 441 insertions, 91 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs index e808bea..6f28cff 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs | |||
| @@ -31,6 +31,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, P | |||
| 31 | use mockall::*; | 31 | use mockall::*; |
| 32 | use nostr::{ | 32 | use nostr::{ |
| 33 | Event, | 33 | Event, |
| 34 | event::{TagKind, TagStandard, UnsignedEvent}, | ||
| 34 | filter::Alphabet, | 35 | filter::Alphabet, |
| 35 | nips::{nip01::Coordinate, nip19::Nip19Coordinate}, | 36 | nips::{nip01::Coordinate, nip19::Nip19Coordinate}, |
| 36 | signer::SignerBackend, | 37 | signer::SignerBackend, |
| @@ -47,20 +48,23 @@ use crate::{ | |||
| 47 | get_dirs, | 48 | get_dirs, |
| 48 | git::{Repo, RepoActions, get_git_config_item}, | 49 | git::{Repo, RepoActions, get_git_config_item}, |
| 49 | git_events::{ | 50 | git_events::{ |
| 50 | event_is_cover_letter, event_is_patch_set_root, event_is_revision_root, status_kinds, | 51 | KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_is_cover_letter, |
| 52 | event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update, | ||
| 53 | status_kinds, | ||
| 51 | }, | 54 | }, |
| 52 | login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, | 55 | login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, |
| 53 | repo_ref::RepoRef, | 56 | repo_ref::{RepoRef, normalize_grasp_server_url}, |
| 54 | repo_state::RepoState, | 57 | repo_state::RepoState, |
| 55 | }; | 58 | }; |
| 56 | 59 | ||
| 57 | #[allow(clippy::struct_field_names)] | 60 | #[allow(clippy::struct_field_names)] |
| 58 | pub struct Client { | 61 | pub struct Client { |
| 59 | client: nostr_sdk::Client, | 62 | client: nostr_sdk::Client, |
| 60 | fallback_relays: Vec<String>, | 63 | relay_default_set: Vec<String>, |
| 61 | more_fallback_relays: Vec<String>, | 64 | more_fallback_relays: Vec<String>, |
| 62 | blaster_relays: Vec<String>, | 65 | blaster_relays: Vec<String>, |
| 63 | fallback_signer_relays: Vec<String>, | 66 | fallback_signer_relays: Vec<String>, |
| 67 | grasp_default_set: Vec<String>, | ||
| 64 | relays_not_to_retry: Arc<RwLock<HashMap<RelayUrl, String>>>, | 68 | relays_not_to_retry: Arc<RwLock<HashMap<RelayUrl, String>>>, |
| 65 | } | 69 | } |
| 66 | 70 | ||
| @@ -94,10 +98,11 @@ pub trait Connect { | |||
| 94 | async fn set_signer(&mut self, signer: Arc<dyn NostrSigner>); | 98 | async fn set_signer(&mut self, signer: Arc<dyn NostrSigner>); |
| 95 | async fn connect(&self, relay_url: &RelayUrl) -> Result<()>; | 99 | async fn connect(&self, relay_url: &RelayUrl) -> Result<()>; |
| 96 | async fn disconnect(&self) -> Result<()>; | 100 | async fn disconnect(&self) -> Result<()>; |
| 97 | fn get_fallback_relays(&self) -> &Vec<String>; | 101 | fn get_relay_default_set(&self) -> &Vec<String>; |
| 98 | fn get_more_fallback_relays(&self) -> &Vec<String>; | 102 | fn get_more_fallback_relays(&self) -> &Vec<String>; |
| 99 | fn get_blaster_relays(&self) -> &Vec<String>; | 103 | fn get_blaster_relays(&self) -> &Vec<String>; |
| 100 | fn get_fallback_signer_relays(&self) -> &Vec<String>; | 104 | fn get_fallback_signer_relays(&self) -> &Vec<String>; |
| 105 | fn get_grasp_default_set(&self) -> &Vec<String>; | ||
| 101 | async fn send_event_to<'a>( | 106 | async fn send_event_to<'a>( |
| 102 | &self, | 107 | &self, |
| 103 | git_repo_path: Option<&'a Path>, | 108 | git_repo_path: Option<&'a Path>, |
| @@ -147,10 +152,11 @@ impl Connect for Client { | |||
| 147 | .opts(Options::new().relay_limits(RelayLimits::disable())) | 152 | .opts(Options::new().relay_limits(RelayLimits::disable())) |
| 148 | .build() | 153 | .build() |
| 149 | }, | 154 | }, |
| 150 | fallback_relays: opts.fallback_relays, | 155 | relay_default_set: opts.relay_default_set, |
| 151 | more_fallback_relays: opts.more_fallback_relays, | 156 | more_fallback_relays: opts.more_fallback_relays, |
| 152 | blaster_relays: opts.blaster_relays, | 157 | blaster_relays: opts.blaster_relays, |
| 153 | fallback_signer_relays: opts.fallback_signer_relays, | 158 | fallback_signer_relays: opts.fallback_signer_relays, |
| 159 | grasp_default_set: opts.grasp_default_set, | ||
| 154 | relays_not_to_retry: Arc::new(RwLock::new(HashMap::new())), | 160 | relays_not_to_retry: Arc::new(RwLock::new(HashMap::new())), |
| 155 | } | 161 | } |
| 156 | } | 162 | } |
| @@ -189,8 +195,8 @@ impl Connect for Client { | |||
| 189 | Ok(()) | 195 | Ok(()) |
| 190 | } | 196 | } |
| 191 | 197 | ||
| 192 | fn get_fallback_relays(&self) -> &Vec<String> { | 198 | fn get_relay_default_set(&self) -> &Vec<String> { |
| 193 | &self.fallback_relays | 199 | &self.relay_default_set |
| 194 | } | 200 | } |
| 195 | 201 | ||
| 196 | fn get_more_fallback_relays(&self) -> &Vec<String> { | 202 | fn get_more_fallback_relays(&self) -> &Vec<String> { |
| @@ -205,6 +211,10 @@ impl Connect for Client { | |||
| 205 | &self.fallback_signer_relays | 211 | &self.fallback_signer_relays |
| 206 | } | 212 | } |
| 207 | 213 | ||
| 214 | fn get_grasp_default_set(&self) -> &Vec<String> { | ||
| 215 | &self.grasp_default_set | ||
| 216 | } | ||
| 217 | |||
| 208 | async fn send_event_to<'a>( | 218 | async fn send_event_to<'a>( |
| 209 | &self, | 219 | &self, |
| 210 | git_repo_path: Option<&'a Path>, | 220 | git_repo_path: Option<&'a Path>, |
| @@ -335,8 +345,8 @@ impl Connect for Client { | |||
| 335 | trusted_maintainer_coordinate: Option<&'a Nip19Coordinate>, | 345 | trusted_maintainer_coordinate: Option<&'a Nip19Coordinate>, |
| 336 | user_profiles: &HashSet<PublicKey>, | 346 | user_profiles: &HashSet<PublicKey>, |
| 337 | ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)> { | 347 | ) -> Result<(Vec<Result<FetchReport>>, MultiProgress)> { |
| 338 | let fallback_relays = &self | 348 | let relay_default_set = &self |
| 339 | .fallback_relays | 349 | .relay_default_set |
| 340 | .iter() | 350 | .iter() |
| 341 | .filter_map(|r| RelayUrl::parse(r).ok()) | 351 | .filter_map(|r| RelayUrl::parse(r).ok()) |
| 342 | .collect::<HashSet<RelayUrl>>(); | 352 | .collect::<HashSet<RelayUrl>>(); |
| @@ -345,7 +355,7 @@ impl Connect for Client { | |||
| 345 | git_repo_path, | 355 | git_repo_path, |
| 346 | trusted_maintainer_coordinate, | 356 | trusted_maintainer_coordinate, |
| 347 | user_profiles, | 357 | user_profiles, |
| 348 | fallback_relays.clone(), | 358 | relay_default_set.clone(), |
| 349 | ) | 359 | ) |
| 350 | .await?; | 360 | .await?; |
| 351 | 361 | ||
| @@ -685,17 +695,18 @@ async fn get_events_of( | |||
| 685 | 695 | ||
| 686 | pub struct Params { | 696 | pub struct Params { |
| 687 | pub keys: Option<nostr::Keys>, | 697 | pub keys: Option<nostr::Keys>, |
| 688 | pub fallback_relays: Vec<String>, | 698 | pub relay_default_set: Vec<String>, |
| 689 | pub more_fallback_relays: Vec<String>, | 699 | pub more_fallback_relays: Vec<String>, |
| 690 | pub blaster_relays: Vec<String>, | 700 | pub blaster_relays: Vec<String>, |
| 691 | pub fallback_signer_relays: Vec<String>, | 701 | pub fallback_signer_relays: Vec<String>, |
| 702 | pub grasp_default_set: Vec<String>, | ||
| 692 | } | 703 | } |
| 693 | 704 | ||
| 694 | impl Default for Params { | 705 | impl Default for Params { |
| 695 | fn default() -> Self { | 706 | fn default() -> Self { |
| 696 | Params { | 707 | Params { |
| 697 | keys: None, | 708 | keys: None, |
| 698 | fallback_relays: if std::env::var("NGITTEST").is_ok() { | 709 | relay_default_set: if std::env::var("NGITTEST").is_ok() { |
| 699 | vec![ | 710 | vec![ |
| 700 | "ws://localhost:8051".to_string(), | 711 | "ws://localhost:8051".to_string(), |
| 701 | "ws://localhost:8052".to_string(), | 712 | "ws://localhost:8052".to_string(), |
| @@ -731,6 +742,11 @@ impl Default for Params { | |||
| 731 | } else { | 742 | } else { |
| 732 | vec!["wss://relay.nsec.app".to_string()] | 743 | vec!["wss://relay.nsec.app".to_string()] |
| 733 | }, | 744 | }, |
| 745 | grasp_default_set: if std::env::var("NGITTEST").is_ok() { | ||
| 746 | vec![] | ||
| 747 | } else { | ||
| 748 | vec!["relay.ngit.dev".to_string(), "gitnostr.com".to_string()] | ||
| 749 | }, | ||
| 734 | } | 750 | } |
| 735 | } | 751 | } |
| 736 | } | 752 | } |
| @@ -749,7 +765,7 @@ impl Params { | |||
| 749 | .collect(); | 765 | .collect(); |
| 750 | // elsewhere it is assumed this isn't empty | 766 | // elsewhere it is assumed this isn't empty |
| 751 | if !new_default_relays.is_empty() { | 767 | if !new_default_relays.is_empty() { |
| 752 | params.fallback_relays = new_default_relays; | 768 | params.relay_default_set = new_default_relays; |
| 753 | } | 769 | } |
| 754 | } | 770 | } |
| 755 | if let Ok(Some(relay_blasters)) = | 771 | if let Ok(Some(relay_blasters)) = |
| @@ -770,6 +786,17 @@ impl Params { | |||
| 770 | .map(|relay_url| relay_url.to_string()) // Convert RelayUrl back to String | 786 | .map(|relay_url| relay_url.to_string()) // Convert RelayUrl back to String |
| 771 | .collect(); | 787 | .collect(); |
| 772 | } | 788 | } |
| 789 | if let Ok(Some(grasp_default_servers)) = | ||
| 790 | get_git_config_item(git_repo, "nostr.grasp-default-set") | ||
| 791 | { | ||
| 792 | let new_default_grasp_servers: Vec<String> = grasp_default_servers | ||
| 793 | .split(';') | ||
| 794 | .filter_map(|url| normalize_grasp_server_url(url).ok()) // Attempt to parse and filter out errors | ||
| 795 | .collect(); | ||
| 796 | if !new_default_grasp_servers.is_empty() { | ||
| 797 | params.grasp_default_set = new_default_grasp_servers; | ||
| 798 | } | ||
| 799 | } | ||
| 773 | } | 800 | } |
| 774 | params | 801 | params |
| 775 | } | 802 | } |
| @@ -811,6 +838,30 @@ pub async fn sign_event( | |||
| 811 | } | 838 | } |
| 812 | } | 839 | } |
| 813 | 840 | ||
| 841 | pub async fn sign_draft_event( | ||
| 842 | draft_event: UnsignedEvent, | ||
| 843 | signer: &Arc<dyn NostrSigner>, | ||
| 844 | description: String, | ||
| 845 | ) -> Result<nostr::Event> { | ||
| 846 | if signer.backend() == SignerBackend::NostrConnect { | ||
| 847 | let term = console::Term::stderr(); | ||
| 848 | term.write_line(&format!( | ||
| 849 | "signing event ({description}) with remote signer..." | ||
| 850 | ))?; | ||
| 851 | let event = signer | ||
| 852 | .sign_event(draft_event) | ||
| 853 | .await | ||
| 854 | .context("failed to sign event")?; | ||
| 855 | term.clear_last_lines(1)?; | ||
| 856 | Ok(event) | ||
| 857 | } else { | ||
| 858 | signer | ||
| 859 | .sign_event(draft_event) | ||
| 860 | .await | ||
| 861 | .context("failed to sign event") | ||
| 862 | } | ||
| 863 | } | ||
| 864 | |||
| 814 | pub async fn fetch_public_key(signer: &Arc<dyn NostrSigner>) -> Result<nostr::PublicKey> { | 865 | pub async fn fetch_public_key(signer: &Arc<dyn NostrSigner>) -> Result<nostr::PublicKey> { |
| 815 | if signer.backend() == SignerBackend::NostrConnect { | 866 | if signer.backend() == SignerBackend::NostrConnect { |
| 816 | let term = console::Term::stderr(); | 867 | let term = console::Term::stderr(); |
| @@ -1459,7 +1510,7 @@ async fn process_fetched_events( | |||
| 1459 | report.updated_state = Some((event.created_at, event.id)); | 1510 | report.updated_state = Some((event.created_at, event.id)); |
| 1460 | } | 1511 | } |
| 1461 | } | 1512 | } |
| 1462 | } else if event_is_patch_set_root(event) { | 1513 | } else if event_is_patch_set_root(event) || event.kind.eq(&KIND_PULL_REQUEST) { |
| 1463 | fresh_proposal_roots.insert(event.id); | 1514 | fresh_proposal_roots.insert(event.id); |
| 1464 | report.proposals.insert(event.id); | 1515 | report.proposals.insert(event.id); |
| 1465 | if !request.contributors.contains(&event.pubkey) | 1516 | if !request.contributors.contains(&event.pubkey) |
| @@ -1487,12 +1538,23 @@ async fn process_fetched_events( | |||
| 1487 | } | 1538 | } |
| 1488 | for event in &events { | 1539 | for event in &events { |
| 1489 | if !request.existing_events.contains(&event.id) | 1540 | if !request.existing_events.contains(&event.id) |
| 1490 | && !event | 1541 | && (!event |
| 1491 | .tags | 1542 | .tags |
| 1492 | .event_ids() | 1543 | .event_ids() |
| 1493 | .any(|id| report.proposals.contains(id)) | 1544 | .any(|id| report.proposals.contains(id)) |
| 1545 | || event | ||
| 1546 | .tags | ||
| 1547 | .filter_standardized(TagKind::Custom(std::borrow::Cow::Borrowed("E"))) | ||
| 1548 | .filter_map(|t| match t { | ||
| 1549 | TagStandard::Event { event_id, .. } => Some(event_id), | ||
| 1550 | TagStandard::EventReport(event_id, ..) => Some(event_id), | ||
| 1551 | _ => None, | ||
| 1552 | }) | ||
| 1553 | .any(|id| report.proposals.contains(id))) | ||
| 1494 | { | 1554 | { |
| 1495 | if event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event) { | 1555 | if (event.kind.eq(&Kind::GitPatch) && !event_is_patch_set_root(event)) |
| 1556 | || event.kind.eq(&KIND_PULL_REQUEST_UPDATE) | ||
| 1557 | { | ||
| 1496 | report.commits.insert(event.id); | 1558 | report.commits.insert(event.id); |
| 1497 | } else if status_kinds().contains(&event.kind) { | 1559 | } else if status_kinds().contains(&event.kind) { |
| 1498 | report.statuses.insert(event.id); | 1560 | report.statuses.insert(event.id); |
| @@ -1570,7 +1632,7 @@ pub fn get_fetch_filters( | |||
| 1570 | get_filter_state_events(repo_coordinates), | 1632 | get_filter_state_events(repo_coordinates), |
| 1571 | get_filter_repo_events(repo_coordinates), | 1633 | get_filter_repo_events(repo_coordinates), |
| 1572 | nostr::Filter::default() | 1634 | nostr::Filter::default() |
| 1573 | .kinds(vec![Kind::GitPatch, Kind::EventDeletion]) | 1635 | .kinds(vec![Kind::GitPatch, Kind::EventDeletion, KIND_PULL_REQUEST]) |
| 1574 | .custom_tags( | 1636 | .custom_tags( |
| 1575 | SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), | 1637 | SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), |
| 1576 | repo_coordinates | 1638 | repo_coordinates |
| @@ -1584,15 +1646,29 @@ pub fn get_fetch_filters( | |||
| 1584 | vec![] | 1646 | vec![] |
| 1585 | } else { | 1647 | } else { |
| 1586 | vec![ | 1648 | vec![ |
| 1587 | nostr::Filter::default() | 1649 | nostr::Filter::default().events(proposal_ids.clone()).kinds( |
| 1588 | .events(proposal_ids.clone()) | 1650 | [ |
| 1589 | .kinds([vec![Kind::GitPatch, Kind::EventDeletion], status_kinds()].concat()), | 1651 | vec![ |
| 1652 | Kind::GitPatch, | ||
| 1653 | Kind::EventDeletion, | ||
| 1654 | KIND_PULL_REQUEST_UPDATE, | ||
| 1655 | ], | ||
| 1656 | status_kinds(), | ||
| 1657 | ] | ||
| 1658 | .concat(), | ||
| 1659 | ), | ||
| 1590 | nostr::Filter::default() | 1660 | nostr::Filter::default() |
| 1591 | .custom_tags( | 1661 | .custom_tags( |
| 1592 | SingleLetterTag::uppercase(Alphabet::E), | 1662 | SingleLetterTag::uppercase(Alphabet::E), |
| 1593 | proposal_ids.clone(), | 1663 | proposal_ids.clone(), |
| 1594 | ) | 1664 | ) |
| 1595 | .kinds([vec![Kind::GitPatch, Kind::EventDeletion], status_kinds()].concat()), | 1665 | .kinds( |
| 1666 | [ | ||
| 1667 | vec![Kind::EventDeletion, KIND_PULL_REQUEST_UPDATE], | ||
| 1668 | status_kinds(), | ||
| 1669 | ] | ||
| 1670 | .concat(), | ||
| 1671 | ), | ||
| 1596 | ] | 1672 | ] |
| 1597 | }, | 1673 | }, |
| 1598 | if required_profiles.is_empty() { | 1674 | if required_profiles.is_empty() { |
| @@ -1784,7 +1860,7 @@ pub async fn get_proposals_and_revisions_from_cache( | |||
| 1784 | git_repo_path, | 1860 | git_repo_path, |
| 1785 | vec![ | 1861 | vec![ |
| 1786 | nostr::Filter::default() | 1862 | nostr::Filter::default() |
| 1787 | .kind(nostr::Kind::GitPatch) | 1863 | .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST]) |
| 1788 | .custom_tags( | 1864 | .custom_tags( |
| 1789 | nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), | 1865 | nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), |
| 1790 | repo_coordinates | 1866 | repo_coordinates |
| @@ -1796,7 +1872,8 @@ pub async fn get_proposals_and_revisions_from_cache( | |||
| 1796 | ) | 1872 | ) |
| 1797 | .await? | 1873 | .await? |
| 1798 | .iter() | 1874 | .iter() |
| 1799 | .filter(|e| event_is_patch_set_root(e)) | 1875 | .filter(|e| event_is_patch_set_root(e) || e.kind.eq(&KIND_PULL_REQUEST)) |
| 1876 | .filter(|e| e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e)) | ||
| 1800 | .cloned() | 1877 | .cloned() |
| 1801 | .collect::<Vec<nostr::Event>>(); | 1878 | .collect::<Vec<nostr::Event>>(); |
| 1802 | proposals.sort_by_key(|e| e.created_at); | 1879 | proposals.sort_by_key(|e| e.created_at); |
| @@ -1804,7 +1881,7 @@ pub async fn get_proposals_and_revisions_from_cache( | |||
| 1804 | Ok(proposals) | 1881 | Ok(proposals) |
| 1805 | } | 1882 | } |
| 1806 | 1883 | ||
| 1807 | pub async fn get_all_proposal_patch_events_from_cache( | 1884 | pub async fn get_all_proposal_patch_pr_pr_update_events_from_cache( |
| 1808 | git_repo_path: &Path, | 1885 | git_repo_path: &Path, |
| 1809 | repo_ref: &RepoRef, | 1886 | repo_ref: &RepoRef, |
| 1810 | proposal_id: &nostr::EventId, | 1887 | proposal_id: &nostr::EventId, |
| @@ -1813,10 +1890,21 @@ pub async fn get_all_proposal_patch_events_from_cache( | |||
| 1813 | git_repo_path, | 1890 | git_repo_path, |
| 1814 | vec![ | 1891 | vec![ |
| 1815 | nostr::Filter::default() | 1892 | nostr::Filter::default() |
| 1816 | .kind(nostr::Kind::GitPatch) | 1893 | .kinds([ |
| 1894 | nostr::Kind::GitPatch, | ||
| 1895 | KIND_PULL_REQUEST, | ||
| 1896 | KIND_PULL_REQUEST_UPDATE, | ||
| 1897 | ]) | ||
| 1817 | .event(*proposal_id), | 1898 | .event(*proposal_id), |
| 1818 | nostr::Filter::default() | 1899 | nostr::Filter::default() |
| 1819 | .kind(nostr::Kind::GitPatch) | 1900 | .kinds([ |
| 1901 | nostr::Kind::GitPatch, | ||
| 1902 | KIND_PULL_REQUEST, | ||
| 1903 | KIND_PULL_REQUEST_UPDATE, | ||
| 1904 | ]) | ||
| 1905 | .custom_tag(SingleLetterTag::uppercase(Alphabet::E), *proposal_id), | ||
| 1906 | nostr::Filter::default() | ||
| 1907 | .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST]) | ||
| 1820 | .id(*proposal_id), | 1908 | .id(*proposal_id), |
| 1821 | ], | 1909 | ], |
| 1822 | ) | 1910 | ) |
| @@ -1836,7 +1924,11 @@ pub async fn get_all_proposal_patch_events_from_cache( | |||
| 1836 | .iter() | 1924 | .iter() |
| 1837 | .copied() | 1925 | .copied() |
| 1838 | .collect(); | 1926 | .collect(); |
| 1839 | commit_events.retain(|e| permissioned_users.contains(&e.pubkey)); | 1927 | |
| 1928 | commit_events.retain(|e| { | ||
| 1929 | permissioned_users.contains(&e.pubkey) | ||
| 1930 | && (e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e)) | ||
| 1931 | }); | ||
| 1840 | 1932 | ||
| 1841 | let revision_roots: HashSet<nostr::EventId> = commit_events | 1933 | let revision_roots: HashSet<nostr::EventId> = commit_events |
| 1842 | .iter() | 1934 | .iter() |
| @@ -1849,8 +1941,20 @@ pub async fn get_all_proposal_patch_events_from_cache( | |||
| 1849 | git_repo_path, | 1941 | git_repo_path, |
| 1850 | vec![ | 1942 | vec![ |
| 1851 | nostr::Filter::default() | 1943 | nostr::Filter::default() |
| 1852 | .kind(nostr::Kind::GitPatch) | 1944 | .kinds([ |
| 1853 | .events(revision_roots) | 1945 | nostr::Kind::GitPatch, |
| 1946 | KIND_PULL_REQUEST, | ||
| 1947 | KIND_PULL_REQUEST_UPDATE, | ||
| 1948 | ]) | ||
| 1949 | .events(revision_roots.clone()) | ||
| 1950 | .authors(permissioned_users.clone()), | ||
| 1951 | nostr::Filter::default() | ||
| 1952 | .kinds([ | ||
| 1953 | nostr::Kind::GitPatch, | ||
| 1954 | KIND_PULL_REQUEST, | ||
| 1955 | KIND_PULL_REQUEST_UPDATE, | ||
| 1956 | ]) | ||
| 1957 | .custom_tags(SingleLetterTag::uppercase(Alphabet::E), revision_roots) | ||
| 1854 | .authors(permissioned_users.clone()), | 1958 | .authors(permissioned_users.clone()), |
| 1855 | ], | 1959 | ], |
| 1856 | ) | 1960 | ) |
| @@ -1891,7 +1995,7 @@ pub async fn send_events( | |||
| 1891 | silent: bool, | 1995 | silent: bool, |
| 1892 | ) -> Result<()> { | 1996 | ) -> Result<()> { |
| 1893 | let fallback = [ | 1997 | let fallback = [ |
| 1894 | client.get_fallback_relays().clone(), | 1998 | client.get_relay_default_set().clone(), |
| 1895 | if events.iter().any(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) { | 1999 | if events.iter().any(|e| e.kind.eq(&Kind::GitRepoAnnouncement)) { |
| 1896 | client.get_blaster_relays().clone() | 2000 | client.get_blaster_relays().clone() |
| 1897 | } else { | 2001 | } else { |
diff --git a/src/lib/git/mod.rs b/src/lib/git/mod.rs index d4bf2f5..b275b49 100644 --- a/src/lib/git/mod.rs +++ b/src/lib/git/mod.rs | |||
| @@ -10,6 +10,7 @@ use nostr_sdk::{ | |||
| 10 | Tags, | 10 | Tags, |
| 11 | hashes::{Hash, sha1::Hash as Sha1Hash}, | 11 | hashes::{Hash, sha1::Hash as Sha1Hash}, |
| 12 | }; | 12 | }; |
| 13 | use nostr_url::NostrUrlDecoded; | ||
| 13 | 14 | ||
| 14 | use crate::git_events::{get_commit_id_from_patch, tag_value}; | 15 | use crate::git_events::{get_commit_id_from_patch, tag_value}; |
| 15 | pub mod identify_ahead_behind; | 16 | pub mod identify_ahead_behind; |
| @@ -92,6 +93,10 @@ pub trait RepoActions { | |||
| 92 | fn get_git_config_item(&self, item: &str, global: Option<bool>) -> Result<Option<String>>; | 93 | fn get_git_config_item(&self, item: &str, global: Option<bool>) -> Result<Option<String>>; |
| 93 | fn save_git_config_item(&self, item: &str, value: &str, global: bool) -> Result<()>; | 94 | fn save_git_config_item(&self, item: &str, value: &str, global: bool) -> Result<()>; |
| 94 | fn remove_git_config_item(&self, item: &str, global: bool) -> Result<bool>; | 95 | fn remove_git_config_item(&self, item: &str, global: bool) -> Result<bool>; |
| 96 | #[allow(async_fn_in_trait)] | ||
| 97 | async fn get_first_nostr_remote_when_in_ngit_binary( | ||
| 98 | &self, | ||
| 99 | ) -> Result<Option<(String, NostrUrlDecoded)>>; | ||
| 95 | } | 100 | } |
| 96 | 101 | ||
| 97 | impl RepoActions for Repo { | 102 | impl RepoActions for Repo { |
| @@ -796,6 +801,21 @@ impl RepoActions for Repo { | |||
| 796 | Ok(true) | 801 | Ok(true) |
| 797 | } | 802 | } |
| 798 | } | 803 | } |
| 804 | |||
| 805 | async fn get_first_nostr_remote_when_in_ngit_binary( | ||
| 806 | &self, | ||
| 807 | ) -> Result<Option<(String, NostrUrlDecoded)>> { | ||
| 808 | for remote_name in self.git_repo.remotes()?.iter().flatten() { | ||
| 809 | if let Some(remote_url) = self.git_repo.find_remote(remote_name)?.url() { | ||
| 810 | if let Ok(nostr_url_decoded) = | ||
| 811 | NostrUrlDecoded::parse_and_resolve(remote_url, &Some(self)).await | ||
| 812 | { | ||
| 813 | return Ok(Some((remote_name.to_string(), nostr_url_decoded))); | ||
| 814 | } | ||
| 815 | } | ||
| 816 | } | ||
| 817 | Ok(None) | ||
| 818 | } | ||
| 799 | } | 819 | } |
| 800 | 820 | ||
| 801 | fn oid_to_u8_20_bytes(oid: &Oid) -> [u8; 20] { | 821 | fn oid_to_u8_20_bytes(oid: &Oid) -> [u8; 20] { |
diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs index 69406c1..2e1f215 100644 --- a/src/lib/git_events.rs +++ b/src/lib/git_events.rs | |||
| @@ -1,7 +1,10 @@ | |||
| 1 | use std::{str::FromStr, sync::Arc}; | 1 | use std::{str::FromStr, sync::Arc}; |
| 2 | 2 | ||
| 3 | use anyhow::{Context, Result, bail}; | 3 | use anyhow::{Context, Result, bail}; |
| 4 | use nostr::nips::{nip01::Coordinate, nip10::Marker, nip19::Nip19}; | 4 | use nostr::{ |
| 5 | event::UnsignedEvent, | ||
| 6 | nips::{nip01::Coordinate, nip10::Marker, nip19::Nip19}, | ||
| 7 | }; | ||
| 5 | use nostr_sdk::{ | 8 | use nostr_sdk::{ |
| 6 | Event, EventBuilder, EventId, FromBech32, Kind, NostrSigner, PublicKey, Tag, TagKind, | 9 | Event, EventBuilder, EventId, FromBech32, Kind, NostrSigner, PublicKey, Tag, TagKind, |
| 7 | TagStandard, hashes::sha1::Hash as Sha1Hash, | 10 | TagStandard, hashes::sha1::Hash as Sha1Hash, |
| @@ -58,6 +61,9 @@ pub fn status_kinds() -> Vec<Kind> { | |||
| 58 | ] | 61 | ] |
| 59 | } | 62 | } |
| 60 | 63 | ||
| 64 | pub const KIND_PULL_REQUEST: Kind = Kind::Custom(1618); | ||
| 65 | pub const KIND_PULL_REQUEST_UPDATE: Kind = Kind::Custom(1619); | ||
| 66 | |||
| 61 | pub fn event_is_patch_set_root(event: &Event) -> bool { | 67 | pub fn event_is_patch_set_root(event: &Event) -> bool { |
| 62 | event.kind.eq(&Kind::GitPatch) | 68 | event.kind.eq(&Kind::GitPatch) |
| 63 | && event | 69 | && event |
| @@ -67,11 +73,16 @@ pub fn event_is_patch_set_root(event: &Event) -> bool { | |||
| 67 | } | 73 | } |
| 68 | 74 | ||
| 69 | pub fn event_is_revision_root(event: &Event) -> bool { | 75 | pub fn event_is_revision_root(event: &Event) -> bool { |
| 70 | event.kind.eq(&Kind::GitPatch) | 76 | (event.kind.eq(&Kind::GitPatch) |
| 71 | && event | 77 | && event |
| 72 | .tags | 78 | .tags |
| 73 | .iter() | 79 | .iter() |
| 74 | .any(|t| t.as_slice().len() > 1 && t.as_slice()[1].eq("revision-root")) | 80 | .any(|t| t.as_slice().len() > 1 && t.as_slice()[1].eq("revision-root"))) |
| 81 | || (event.kind.eq(&KIND_PULL_REQUEST) | ||
| 82 | && event | ||
| 83 | .tags | ||
| 84 | .iter() | ||
| 85 | .any(|t| t.as_slice().len() > 1 && t.as_slice()[0].eq("e"))) | ||
| 75 | } | 86 | } |
| 76 | 87 | ||
| 77 | pub fn patch_supports_commit_ids(event: &Event) -> bool { | 88 | pub fn patch_supports_commit_ids(event: &Event) -> bool { |
| @@ -82,6 +93,19 @@ pub fn patch_supports_commit_ids(event: &Event) -> bool { | |||
| 82 | .any(|t| !t.as_slice().is_empty() && t.as_slice()[0].eq("commit-pgp-sig")) | 93 | .any(|t| !t.as_slice().is_empty() && t.as_slice()[0].eq("commit-pgp-sig")) |
| 83 | } | 94 | } |
| 84 | 95 | ||
| 96 | pub fn event_is_valid_pr_or_pr_update(event: &Event) -> bool { | ||
| 97 | [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) | ||
| 98 | && event.tags.iter().any(|t| { | ||
| 99 | t.as_slice().len().gt(&1) | ||
| 100 | && t.as_slice()[0].eq("c") | ||
| 101 | && git2::Oid::from_str(&t.as_slice()[1]).is_ok() | ||
| 102 | }) | ||
| 103 | && event | ||
| 104 | .tags | ||
| 105 | .iter() | ||
| 106 | .any(|t| t.as_slice().len().gt(&1) && t.as_slice()[0].eq("clone")) | ||
| 107 | } | ||
| 108 | |||
| 85 | #[allow(clippy::too_many_arguments)] | 109 | #[allow(clippy::too_many_arguments)] |
| 86 | #[allow(clippy::too_many_lines)] | 110 | #[allow(clippy::too_many_lines)] |
| 87 | pub async fn generate_patch_event( | 111 | pub async fn generate_patch_event( |
| @@ -326,6 +350,180 @@ pub fn event_tag_from_nip19_or_hex( | |||
| 326 | } | 350 | } |
| 327 | } | 351 | } |
| 328 | 352 | ||
| 353 | pub fn generate_unsigned_pr_or_update_event( | ||
| 354 | git_repo: &Repo, | ||
| 355 | repo_ref: &RepoRef, | ||
| 356 | signing_public_key: &PublicKey, | ||
| 357 | root_proposal: Option<&Event>, | ||
| 358 | commit: &Sha1Hash, | ||
| 359 | clone_url_hint: &[&str], | ||
| 360 | mentions: &[nostr::Tag], | ||
| 361 | ) -> Result<UnsignedEvent> { | ||
| 362 | let root_patch_cover_letter = if let Some(root_proposal) = root_proposal { | ||
| 363 | if root_proposal.kind.eq(&Kind::GitPatch) { | ||
| 364 | Some(event_to_cover_letter(root_proposal)?) | ||
| 365 | } else { | ||
| 366 | None | ||
| 367 | } | ||
| 368 | } else { | ||
| 369 | None | ||
| 370 | }; | ||
| 371 | |||
| 372 | let title = if let Some(cl) = &root_patch_cover_letter { | ||
| 373 | cl.title.clone() | ||
| 374 | } else { | ||
| 375 | git_repo.get_commit_message_summary(commit)? | ||
| 376 | }; | ||
| 377 | |||
| 378 | let description = if let Some(cl) = &root_patch_cover_letter { | ||
| 379 | cl.description.clone() | ||
| 380 | } else { | ||
| 381 | let mut description = git_repo.get_commit_message(commit)?.trim().to_string(); | ||
| 382 | if let Some(remaining_description) = description.strip_prefix(&title) { | ||
| 383 | description = remaining_description.trim().to_string(); | ||
| 384 | } | ||
| 385 | description | ||
| 386 | }; | ||
| 387 | |||
| 388 | let root_commit = git_repo | ||
| 389 | .get_root_commit() | ||
| 390 | .context("failed to get root commit of the repository")?; | ||
| 391 | |||
| 392 | let pr_update_specific_tags = |root_proposal: &Event| { | ||
| 393 | vec![ | ||
| 394 | Tag::custom( | ||
| 395 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")), | ||
| 396 | vec![format!("git Pull Request Update")], | ||
| 397 | ), | ||
| 398 | Tag::custom( | ||
| 399 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("E")), | ||
| 400 | vec![root_proposal.id], | ||
| 401 | ), | ||
| 402 | Tag::custom( | ||
| 403 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("P")), | ||
| 404 | vec![root_proposal.pubkey], | ||
| 405 | ), | ||
| 406 | ] | ||
| 407 | }; | ||
| 408 | let pr_specific_tags = || { | ||
| 409 | [ | ||
| 410 | vec![ | ||
| 411 | Tag::from_standardized(TagStandard::Subject(title.clone())), | ||
| 412 | Tag::custom( | ||
| 413 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")), | ||
| 414 | vec![format!("git Pull Request: {}", title.clone())], | ||
| 415 | ), | ||
| 416 | ], | ||
| 417 | if let Some(cl) = &root_patch_cover_letter { | ||
| 418 | vec![ | ||
| 419 | Tag::custom( | ||
| 420 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("e")), | ||
| 421 | vec![root_proposal.unwrap().id], | ||
| 422 | ), | ||
| 423 | Tag::custom( | ||
| 424 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("branch-name")), | ||
| 425 | vec![cl.branch_name_without_id_or_prefix.clone()], | ||
| 426 | ), | ||
| 427 | Tag::public_key(root_proposal.unwrap().pubkey), | ||
| 428 | ] | ||
| 429 | } else if let Some(branch_name_tag) = | ||
| 430 | make_branch_name_tag_from_check_out_branch(git_repo) | ||
| 431 | { | ||
| 432 | vec![branch_name_tag] | ||
| 433 | } else { | ||
| 434 | vec![] | ||
| 435 | }, | ||
| 436 | ] | ||
| 437 | .concat() | ||
| 438 | }; | ||
| 439 | |||
| 440 | Ok( | ||
| 441 | if root_proposal.is_some() && root_patch_cover_letter.is_none() { | ||
| 442 | EventBuilder::new(KIND_PULL_REQUEST_UPDATE, "") | ||
| 443 | } else { | ||
| 444 | EventBuilder::new(KIND_PULL_REQUEST, description) | ||
| 445 | } | ||
| 446 | .tags( | ||
| 447 | [ | ||
| 448 | repo_ref | ||
| 449 | .maintainers | ||
| 450 | .iter() | ||
| 451 | .map(|m| { | ||
| 452 | Tag::from_standardized(TagStandard::Coordinate { | ||
| 453 | coordinate: Coordinate { | ||
| 454 | kind: nostr::Kind::GitRepoAnnouncement, | ||
| 455 | public_key: *m, | ||
| 456 | identifier: repo_ref.identifier.to_string(), | ||
| 457 | }, | ||
| 458 | relay_url: repo_ref.relays.first().cloned(), | ||
| 459 | uppercase: false, | ||
| 460 | }) | ||
| 461 | }) | ||
| 462 | .collect::<Vec<Tag>>(), | ||
| 463 | mentions.to_vec(), | ||
| 464 | if let Some(root_proposal) = root_proposal { | ||
| 465 | if root_patch_cover_letter.is_none() { | ||
| 466 | pr_update_specific_tags(root_proposal) | ||
| 467 | } else { | ||
| 468 | pr_specific_tags() | ||
| 469 | } | ||
| 470 | } else { | ||
| 471 | pr_specific_tags() | ||
| 472 | }, | ||
| 473 | vec![ | ||
| 474 | Tag::from_standardized(TagStandard::Reference(format!("{root_commit}"))), | ||
| 475 | Tag::custom( | ||
| 476 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("c")), | ||
| 477 | vec![format!("{commit}")], | ||
| 478 | ), | ||
| 479 | Tag::custom( | ||
| 480 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("clone")), | ||
| 481 | clone_url_hint | ||
| 482 | .iter() | ||
| 483 | .map(|s| s.to_string()) | ||
| 484 | .collect::<Vec<String>>(), | ||
| 485 | ), | ||
| 486 | ], | ||
| 487 | repo_ref | ||
| 488 | .maintainers | ||
| 489 | .iter() | ||
| 490 | .map(|pk| Tag::public_key(*pk)) | ||
| 491 | .collect(), | ||
| 492 | ] | ||
| 493 | .concat(), | ||
| 494 | ) | ||
| 495 | .build(*signing_public_key), | ||
| 496 | ) | ||
| 497 | } | ||
| 498 | |||
| 499 | fn make_branch_name_tag_from_check_out_branch(git_repo: &Repo) -> Option<Tag> { | ||
| 500 | if let Ok(branch_name) = git_repo.get_checked_out_branch_name() { | ||
| 501 | if !branch_name.eq("main") | ||
| 502 | && !branch_name.eq("master") | ||
| 503 | && !branch_name.eq("origin/main") | ||
| 504 | && !branch_name.eq("origin/master") | ||
| 505 | { | ||
| 506 | Some(Tag::custom( | ||
| 507 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("branch-name")), | ||
| 508 | vec![ | ||
| 509 | if let Some(branch_name) = branch_name.strip_prefix("pr/") { | ||
| 510 | branch_name.to_string() | ||
| 511 | } else { | ||
| 512 | branch_name | ||
| 513 | } | ||
| 514 | .chars() | ||
| 515 | .take(60) | ||
| 516 | .collect::<String>(), | ||
| 517 | ], | ||
| 518 | )) | ||
| 519 | } else { | ||
| 520 | None | ||
| 521 | } | ||
| 522 | } else { | ||
| 523 | None | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 329 | #[allow(clippy::too_many_lines)] | 527 | #[allow(clippy::too_many_lines)] |
| 330 | pub async fn generate_cover_letter_and_patch_events( | 528 | pub async fn generate_cover_letter_and_patch_events( |
| 331 | cover_letter_title_description: Option<(String, String)>, | 529 | cover_letter_title_description: Option<(String, String)>, |
| @@ -388,24 +586,8 @@ pub async fn generate_cover_letter_and_patch_events( | |||
| 388 | // eventually a prefix will be needed of the event id to stop 2 proposals with the same name colliding | 586 | // eventually a prefix will be needed of the event id to stop 2 proposals with the same name colliding |
| 389 | // a change like this, or the removal of this tag will require the actual branch name to be tracked | 587 | // a change like this, or the removal of this tag will require the actual branch name to be tracked |
| 390 | // so pulling and pushing still work | 588 | // so pulling and pushing still work |
| 391 | if let Ok(branch_name) = git_repo.get_checked_out_branch_name() { | 589 | if let Some(branch_name_tag) = make_branch_name_tag_from_check_out_branch(git_repo) { |
| 392 | if !branch_name.eq("main") | 590 | vec![branch_name_tag] |
| 393 | && !branch_name.eq("master") | ||
| 394 | && !branch_name.eq("origin/main") | ||
| 395 | && !branch_name.eq("origin/master") | ||
| 396 | { | ||
| 397 | vec![ | ||
| 398 | Tag::custom( | ||
| 399 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("branch-name")), | ||
| 400 | vec![if let Some(branch_name) = branch_name.strip_prefix("pr/") { | ||
| 401 | branch_name.to_string() | ||
| 402 | } else { | ||
| 403 | branch_name | ||
| 404 | }.chars().take(60).collect::<String>()], | ||
| 405 | ), | ||
| 406 | ] | ||
| 407 | } | ||
| 408 | else { vec![] } | ||
| 409 | } else { | 591 | } else { |
| 410 | vec![] | 592 | vec![] |
| 411 | }, | 593 | }, |
| @@ -531,13 +713,22 @@ pub fn commit_msg_from_patch_oneliner(patch: &nostr::Event) -> Result<String> { | |||
| 531 | } | 713 | } |
| 532 | 714 | ||
| 533 | pub fn event_to_cover_letter(event: &nostr::Event) -> Result<CoverLetter> { | 715 | pub fn event_to_cover_letter(event: &nostr::Event) -> Result<CoverLetter> { |
| 534 | if !event_is_patch_set_root(event) { | 716 | if !event.kind.eq(&KIND_PULL_REQUEST) && !event_is_patch_set_root(event) { |
| 535 | bail!("event is not a patch set root event (root patch or cover letter)") | 717 | bail!("event is not a patch set root event (root patch or cover letter)") |
| 536 | } | 718 | } |
| 537 | 719 | ||
| 538 | let title = commit_msg_from_patch_oneliner(event)?; | 720 | let title = if event.kind.eq(&KIND_PULL_REQUEST) { |
| 539 | let full = commit_msg_from_patch(event)?; | 721 | tag_value(event, "subject").unwrap_or("untitled".to_owned()) |
| 540 | let description = full[title.len()..].trim().to_string(); | 722 | } else { |
| 723 | commit_msg_from_patch_oneliner(event)? | ||
| 724 | }; | ||
| 725 | let description = if event.kind.eq(&KIND_PULL_REQUEST) { | ||
| 726 | event.content.clone() | ||
| 727 | } else { | ||
| 728 | commit_msg_from_patch(event)?[title.len()..] | ||
| 729 | .trim() | ||
| 730 | .to_string() | ||
| 731 | }; | ||
| 541 | 732 | ||
| 542 | Ok(CoverLetter { | 733 | Ok(CoverLetter { |
| 543 | title: title.clone(), | 734 | title: title.clone(), |
| @@ -569,25 +760,25 @@ fn safe_branch_name_for_pr(s: &str) -> String { | |||
| 569 | .collect() | 760 | .collect() |
| 570 | } | 761 | } |
| 571 | 762 | ||
| 572 | pub fn get_most_recent_patch_with_ancestors( | 763 | pub fn get_pr_tip_event_or_most_recent_patch_with_ancestors( |
| 573 | mut patches: Vec<nostr::Event>, | 764 | mut proposal_events: Vec<nostr::Event>, |
| 574 | ) -> Result<Vec<nostr::Event>> { | 765 | ) -> Result<Vec<nostr::Event>> { |
| 575 | patches.sort_by_key(|e| e.created_at); | 766 | proposal_events.sort_by_key(|e| e.created_at); |
| 576 | 767 | ||
| 577 | let youngest_patch = patches.last().context("no patches found")?; | 768 | let youngest = proposal_events.last().context("no proposal events found")?; |
| 578 | 769 | ||
| 579 | let patches_with_youngest_created_at: Vec<&nostr::Event> = patches | 770 | let events_with_youngest_created_at: Vec<&nostr::Event> = proposal_events |
| 580 | .iter() | 771 | .iter() |
| 581 | .filter(|p| p.created_at.eq(&youngest_patch.created_at)) | 772 | .filter(|p| p.created_at.eq(&youngest.created_at)) |
| 582 | .collect(); | 773 | .collect(); |
| 583 | 774 | ||
| 584 | let mut res = vec![]; | 775 | let mut res = vec![]; |
| 585 | 776 | ||
| 586 | let mut event_id_to_search = patches_with_youngest_created_at | 777 | let mut event_id_to_search = events_with_youngest_created_at |
| 587 | .clone() | 778 | .clone() |
| 588 | .iter() | 779 | .iter() |
| 589 | .find(|p| { | 780 | .find(|p| { |
| 590 | !patches_with_youngest_created_at.iter().any(|p2| { | 781 | !events_with_youngest_created_at.iter().any(|p2| { |
| 591 | if let Ok(reply_to) = get_event_parent_id(p2) { | 782 | if let Ok(reply_to) = get_event_parent_id(p2) { |
| 592 | reply_to.eq(&p.id.to_string()) | 783 | reply_to.eq(&p.id.to_string()) |
| 593 | } else { | 784 | } else { |
| @@ -595,16 +786,18 @@ pub fn get_most_recent_patch_with_ancestors( | |||
| 595 | } | 786 | } |
| 596 | }) | 787 | }) |
| 597 | }) | 788 | }) |
| 598 | .context("failed to find patches_with_youngest_created_at")? | 789 | .context("failed to find events_with_youngest_created_at")? |
| 599 | .id | 790 | .id |
| 600 | .to_string(); | 791 | .to_string(); |
| 601 | 792 | ||
| 602 | while let Some(event) = patches | 793 | while let Some(event) = proposal_events |
| 603 | .iter() | 794 | .iter() |
| 604 | .find(|e| e.id.to_string().eq(&event_id_to_search)) | 795 | .find(|e| e.id.to_string().eq(&event_id_to_search)) |
| 605 | { | 796 | { |
| 606 | res.push(event.clone()); | 797 | res.push(event.clone()); |
| 607 | if event_is_patch_set_root(event) { | 798 | if [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) |
| 799 | || event_is_patch_set_root(event) | ||
| 800 | { | ||
| 608 | break; | 801 | break; |
| 609 | } | 802 | } |
| 610 | event_id_to_search = get_event_parent_id(event).unwrap_or_default(); | 803 | event_id_to_search = get_event_parent_id(event).unwrap_or_default(); |
| @@ -642,7 +835,61 @@ pub fn is_event_proposal_root_for_branch( | |||
| 642 | || cl | 835 | || cl |
| 643 | .get_branch_name_with_pr_prefix_and_shorthand_id() | 836 | .get_branch_name_with_pr_prefix_and_shorthand_id() |
| 644 | .is_ok_and(|s| s.eq(&branch_name)) | 837 | .is_ok_and(|s| s.eq(&branch_name)) |
| 645 | }) && !event_is_revision_root(e)) | 838 | }) && ( |
| 839 | // If we wanted to treat to list Pull Requests that revise a Patch we would do this: | ||
| 840 | // Note: whilst this the the case elsewhere event_is_revision_root is used, there is more to | ||
| 841 | // think about here? | ||
| 842 | // e.kind.eq(&KIND_PULL_REQUEST) || | ||
| 843 | !event_is_revision_root(e) | ||
| 844 | )) | ||
| 845 | } | ||
| 846 | |||
| 847 | pub fn get_status( | ||
| 848 | proposal: &Event, | ||
| 849 | repo_ref: &RepoRef, | ||
| 850 | all_status_in_repo: &[Event], | ||
| 851 | all_pr_roots_in_repo: &[Event], | ||
| 852 | ) -> Kind { | ||
| 853 | let get_direct_status = |proposal: &Event| { | ||
| 854 | if let Some(e) = all_status_in_repo | ||
| 855 | .iter() | ||
| 856 | .filter(|e| { | ||
| 857 | status_kinds().contains(&e.kind) | ||
| 858 | && e.tags.iter().any(|t| { | ||
| 859 | t.as_slice().len() > 1 && t.as_slice()[1].eq(&proposal.id.to_string()) | ||
| 860 | }) | ||
| 861 | && (proposal.pubkey.eq(&e.pubkey) || repo_ref.maintainers.contains(&e.pubkey)) | ||
| 862 | }) | ||
| 863 | .collect::<Vec<&nostr::Event>>() | ||
| 864 | .first() | ||
| 865 | { | ||
| 866 | e.kind | ||
| 867 | } else { | ||
| 868 | Kind::GitStatusOpen | ||
| 869 | } | ||
| 870 | }; | ||
| 871 | let is_proposal_pr_revision_of_patch = |proposal: &Event, patch: &Event| { | ||
| 872 | proposal.kind.eq(&KIND_PULL_REQUEST) | ||
| 873 | && proposal.tags.clone().into_iter().any(|t| { | ||
| 874 | t.as_slice().len() > 1 | ||
| 875 | && t.as_slice()[0].eq("e") | ||
| 876 | && t.as_slice()[1].eq(&patch.id.to_string()) | ||
| 877 | }) | ||
| 878 | }; | ||
| 879 | |||
| 880 | let direct_status = get_direct_status(proposal); | ||
| 881 | if direct_status.eq(&Kind::GitStatusClosed) && proposal.kind.eq(&Kind::GitPatch) { | ||
| 882 | if let Some(pr_revision) = all_pr_roots_in_repo | ||
| 883 | .iter() | ||
| 884 | .find(|p| is_proposal_pr_revision_of_patch(p, proposal)) | ||
| 885 | { | ||
| 886 | get_direct_status(pr_revision) | ||
| 887 | } else { | ||
| 888 | direct_status | ||
| 889 | } | ||
| 890 | } else { | ||
| 891 | direct_status | ||
| 892 | } | ||
| 646 | } | 893 | } |
| 647 | 894 | ||
| 648 | #[cfg(test)] | 895 | #[cfg(test)] |
diff --git a/src/lib/login/fresh.rs b/src/lib/login/fresh.rs index a169177..358045a 100644 --- a/src/lib/login/fresh.rs +++ b/src/lib/login/fresh.rs | |||
| @@ -728,7 +728,7 @@ async fn signup( | |||
| 728 | EventBuilder::metadata(&Metadata::new().name(name)).sign_with_keys(&keys)?; | 728 | EventBuilder::metadata(&Metadata::new().name(name)).sign_with_keys(&keys)?; |
| 729 | let relay_list = EventBuilder::relay_list( | 729 | let relay_list = EventBuilder::relay_list( |
| 730 | client | 730 | client |
| 731 | .get_fallback_relays() | 731 | .get_relay_default_set() |
| 732 | .iter() | 732 | .iter() |
| 733 | .map(|s| (RelayUrl::parse(s).unwrap(), None)), | 733 | .map(|s| (RelayUrl::parse(s).unwrap(), None)), |
| 734 | ) | 734 | ) |
| @@ -738,7 +738,7 @@ async fn signup( | |||
| 738 | client, | 738 | client, |
| 739 | None, | 739 | None, |
| 740 | vec![profile, relay_list], | 740 | vec![profile, relay_list], |
| 741 | client.get_fallback_relays().clone(), | 741 | client.get_relay_default_set().clone(), |
| 742 | vec![], | 742 | vec![], |
| 743 | true, | 743 | true, |
| 744 | false, | 744 | false, |
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs index 0236e34..bca4a3b 100644 --- a/src/lib/repo_ref.rs +++ b/src/lib/repo_ref.rs | |||
| @@ -309,7 +309,7 @@ impl RepoRef { | |||
| 309 | } | 309 | } |
| 310 | 310 | ||
| 311 | pub fn grasp_servers(&self) -> Vec<String> { | 311 | pub fn grasp_servers(&self) -> Vec<String> { |
| 312 | detect_existing_grasp_servers(Some(self), &[], &[], &[], &self.identifier) | 312 | detect_existing_grasp_servers(Some(self), &[], &[], &self.identifier) |
| 313 | } | 313 | } |
| 314 | } | 314 | } |
| 315 | 315 | ||
| @@ -593,7 +593,6 @@ pub fn detect_existing_grasp_servers( | |||
| 593 | repo_ref: Option<&RepoRef>, | 593 | repo_ref: Option<&RepoRef>, |
| 594 | args_relays: &[String], | 594 | args_relays: &[String], |
| 595 | args_clone_url: &[String], | 595 | args_clone_url: &[String], |
| 596 | args_blossoms: &[String], | ||
| 597 | identifier: &str, | 596 | identifier: &str, |
| 598 | ) -> Vec<String> { | 597 | ) -> Vec<String> { |
| 599 | // Collect clone URLs from arguments or repo_ref | 598 | // Collect clone URLs from arguments or repo_ref |
| @@ -617,18 +616,6 @@ pub fn detect_existing_grasp_servers( | |||
| 617 | Vec::new() | 616 | Vec::new() |
| 618 | }; | 617 | }; |
| 619 | 618 | ||
| 620 | // Collect blossom server URLs from arguments or repo_ref | ||
| 621 | let blossoms: Vec<Url> = if !args_blossoms.is_empty() { | ||
| 622 | args_blossoms | ||
| 623 | .iter() | ||
| 624 | .filter_map(|r| Url::parse(r).ok()) | ||
| 625 | .collect() | ||
| 626 | } else if let Some(repo) = repo_ref { | ||
| 627 | repo.blossoms.clone() | ||
| 628 | } else { | ||
| 629 | Vec::new() | ||
| 630 | }; | ||
| 631 | |||
| 632 | let mut existing_grasp_servers = Vec::new(); | 619 | let mut existing_grasp_servers = Vec::new(); |
| 633 | for url in &clone_urls { | 620 | for url in &clone_urls { |
| 634 | let Ok(formatted_as_grasp_server_url) = normalize_grasp_server_url(url) else { | 621 | let Ok(formatted_as_grasp_server_url) = normalize_grasp_server_url(url) else { |
| @@ -655,14 +642,6 @@ pub fn detect_existing_grasp_servers( | |||
| 655 | continue; | 642 | continue; |
| 656 | } | 643 | } |
| 657 | 644 | ||
| 658 | let matches_blossoms = blossoms.iter().any(|r| { | ||
| 659 | normalize_grasp_server_url(r.as_str()) | ||
| 660 | .is_ok_and(|r| r.eq(&formatted_as_grasp_server_url)) | ||
| 661 | }); | ||
| 662 | if !matches_blossoms { | ||
| 663 | continue; | ||
| 664 | } | ||
| 665 | |||
| 666 | existing_grasp_servers.push(formatted_as_grasp_server_url); | 645 | existing_grasp_servers.push(formatted_as_grasp_server_url); |
| 667 | } | 646 | } |
| 668 | existing_grasp_servers | 647 | existing_grasp_servers |