From 67e25da8c07c4808b8aa41a37b04f112e18abffd Mon Sep 17 00:00:00 2001 From: Awiteb Date: Sun, 3 Aug 2025 00:17:20 +0000 Subject: [PATCH] feat: New flag to `patch apply and merge` to mention patches Signed-off-by: Awiteb --- CHANGELOG.md | 7 ++++ docs/patch/apply.md | 1 + docs/patch/merge.md | 1 + src/cli/commands/patch/apply.rs | 7 +++- src/cli/commands/patch/close.rs | 1 + src/cli/commands/patch/draft.rs | 1 + src/cli/commands/patch/merge.rs | 13 ++++--- src/cli/commands/patch/reopen.rs | 1 + src/cli/common_commands.rs | 61 +++++++++++++++++++++++++------- src/cli/traits.rs | 13 ++++++- src/nostr_utils/mod.rs | 2 +- 11 files changed, 88 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1437b8d..047eed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support signing using NIP-46 bunker - by Awiteb - Keyring the secret key `n34 config keyring --enable` - by Awiteb +- New flag to `patch apply and merge` to mention patches - by Awiteb ### Dependencies - Add `keyring`, `nostr-connect`, `nostr-keyring` and `url` to the dependencies - by Awiteb +- Remove `url` from `n34` dependencies - by Awiteb + +### Documentation + +- N34 book - by Awiteb +- Fix status command docs - by Awiteb ## [0.3.0] - 2025-07-05 diff --git a/docs/patch/apply.md b/docs/patch/apply.md index 4004f9c..28f6415 100644 --- a/docs/patch/apply.md +++ b/docs/patch/apply.md @@ -14,6 +14,7 @@ Arguments: Options: --repo Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel` + --patches Patches that have been applied. Use this when only some patches have been applied, not all ``` Creates a kind `1631` event (Applied/Merged status) for the specified patch. The diff --git a/docs/patch/merge.md b/docs/patch/merge.md index 0a41934..9f87c64 100644 --- a/docs/patch/merge.md +++ b/docs/patch/merge.md @@ -14,6 +14,7 @@ Arguments: Options: --repo Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel` + --patches Patches that have been merged. Use this when only some patches have been merged, not all ``` Creates a kind `1631` event (Applied/Merged status) for the specified patch. The diff --git a/src/cli/commands/patch/apply.rs b/src/cli/commands/patch/apply.rs index 8af6b67..adebbc0 100644 --- a/src/cli/commands/patch/apply.rs +++ b/src/cli/commands/patch/apply.rs @@ -21,7 +21,7 @@ use super::PatchStatus; use crate::{ cli::{ CliOptions, - traits::CommandRunner, + traits::{CommandRunner, VecNostrEventExt}, types::{NaddrOrSet, NostrEvent}, }, error::{N34Error, N34Result}, @@ -38,6 +38,10 @@ pub struct ApplyArgs { /// The open patch id to apply it. Must be orignal root patch or /// revision root patch_id: NostrEvent, + /// Patches that have been applied. Use this when only some patches have + /// been applied, not all. + #[arg(long = "patches", value_name = "PATCH-EVENT-ID")] + applied_patches: Vec, /// The applied commits #[arg(num_args = 1..)] applied_commits: Vec, @@ -51,6 +55,7 @@ impl CommandRunner for ApplyArgs { self.naddrs, PatchStatus::MergedApplied, Some(either::Either::Right(self.applied_commits)), + self.applied_patches.into_event_ids(), |patch_status| { if patch_status.is_merged_or_applied() { return Err(N34Error::InvalidStatus( diff --git a/src/cli/commands/patch/close.rs b/src/cli/commands/patch/close.rs index 31588ed..313cea5 100644 --- a/src/cli/commands/patch/close.rs +++ b/src/cli/commands/patch/close.rs @@ -46,6 +46,7 @@ impl CommandRunner for CloseArgs { self.naddrs, PatchStatus::Closed, None, + Vec::new(), |patch_status| { if patch_status.is_closed() { return Err(N34Error::InvalidStatus( diff --git a/src/cli/commands/patch/draft.rs b/src/cli/commands/patch/draft.rs index 0cedf44..e048bae 100644 --- a/src/cli/commands/patch/draft.rs +++ b/src/cli/commands/patch/draft.rs @@ -46,6 +46,7 @@ impl CommandRunner for DraftArgs { self.naddrs, PatchStatus::Draft, None, + Vec::new(), |patch_status| { if patch_status.is_drafted() { return Err(N34Error::InvalidStatus( diff --git a/src/cli/commands/patch/merge.rs b/src/cli/commands/patch/merge.rs index 8cb0f0b..8b5c846 100644 --- a/src/cli/commands/patch/merge.rs +++ b/src/cli/commands/patch/merge.rs @@ -21,7 +21,7 @@ use super::PatchStatus; use crate::{ cli::{ CliOptions, - traits::CommandRunner, + traits::{CommandRunner, VecNostrEventExt}, types::{NaddrOrSet, NostrEvent}, }, error::{N34Error, N34Result}, @@ -34,12 +34,16 @@ pub struct MergeArgs { /// /// If omitted, looks for a `nostr-address` file. #[arg(value_name = "NADDR-NIP05-OR-SET", long = "repo")] - naddrs: Option>, + naddrs: Option>, /// The open patch id to merge it. Must be orignal root patch or /// revision root - patch_id: NostrEvent, + patch_id: NostrEvent, + /// Patches that have been merged. Use this when only some patches have been + /// merged, not all. + #[arg(long = "patches", value_name = "PATCH-EVENT-ID")] + merged_patches: Vec, /// The merge commit id - merge_commit: Sha1Hash, + merge_commit: Sha1Hash, } impl CommandRunner for MergeArgs { @@ -50,6 +54,7 @@ impl CommandRunner for MergeArgs { self.naddrs, PatchStatus::MergedApplied, Some(either::Either::Left(self.merge_commit)), + self.merged_patches.into_event_ids(), |patch_status| { if patch_status.is_merged_or_applied() { return Err(N34Error::InvalidStatus( diff --git a/src/cli/commands/patch/reopen.rs b/src/cli/commands/patch/reopen.rs index 72b27b8..1fa6337 100644 --- a/src/cli/commands/patch/reopen.rs +++ b/src/cli/commands/patch/reopen.rs @@ -46,6 +46,7 @@ impl CommandRunner for ReopenArgs { self.naddrs, PatchStatus::Open, None, + Vec::new(), |patch_status| { if patch_status.is_open() { return Err(N34Error::InvalidStatus( diff --git a/src/cli/common_commands.rs b/src/cli/common_commands.rs index d2c16ee..f65ce04 100644 --- a/src/cli/common_commands.rs +++ b/src/cli/common_commands.rs @@ -23,6 +23,7 @@ use nostr::{ filter::Filter, hashes::sha1::Hash as Sha1Hash, nips::{nip10::Marker, nip19::ToBech32}, + types::RelayUrl, }; use super::{ @@ -130,6 +131,7 @@ pub async fn patch_status_command( naddrs: Option>, new_status: PatchStatus, merge_or_applied_commits: Option>>, + merge_or_applied_patches: Vec, check_fn: impl FnOnce(&PatchStatus) -> N34Result<()>, ) -> N34Result<()> { let user_pkey = options.pubkey().await?; @@ -209,22 +211,20 @@ pub async fn patch_status_command( .tags(commits.into_iter().map(Tag::reference)); }; - let root = if let Some(root_revision) = root_revision { + if let Some(root_revision) = root_revision { status_builder = status_builder.tag(utils::event_reply_tag( &root_revision, relay_hint.as_ref(), Marker::Reply, )); - root_revision - } else { - root_patch - }; - let patches = client.fetch_patch_series(root, patch_event.pubkey).await?; - status_builder = status_builder.tags( - patches - .into_iter() - .map(|p| utils::event_reply_tag(&p.id, relay_hint.as_ref(), Marker::Mention)), - ); + } + + if !merge_or_applied_patches.is_empty() { + status_builder = status_builder.tags( + build_patches_quote(client.clone(), relay_hint.clone(), merge_or_applied_patches) + .await, + ); + } } let status_event = status_builder.dedup_tags().build(user_pkey); @@ -279,7 +279,6 @@ pub async fn list_patches_and_issues( .add_relays(&client.read_relays_from_users(&authorized_pubkeys).await) .await; - let kind = if list_patches { Kind::GitPatch } else { @@ -352,7 +351,6 @@ pub async fn list_patches_and_issues( Ok(()) } - /// Returns a tuple of (root_id, patch_id) if this is a valid root or revision /// patch. fn get_patch_root_revision(patch_event: &Event) -> N34Result<(EventId, Option)> { @@ -401,3 +399,40 @@ fn format_patch_and_issue(event: &Event, status: Either, + patches: Vec, +) -> Vec { + let client = Arc::new(client); + let relay_hint = Arc::new(relay_hint); + + future::join_all(patches.into_iter().map(|eid| { + let task_relay = Arc::clone(&relay_hint); + let task_client = Arc::clone(&client); + + async move { + Tag::custom( + TagKind::q(), + [ + eid.to_hex(), + task_relay + .as_ref() + .as_ref() + .map(|r| r.to_string()) + .unwrap_or_default(), + task_client + .event_author(eid) + .await + .ok() + .flatten() + .map(|p| p.to_hex()) + .unwrap_or_default(), + ], + ) + } + })) + .await +} diff --git a/src/cli/traits.rs b/src/cli/traits.rs index 917338a..52cc767 100644 --- a/src/cli/traits.rs +++ b/src/cli/traits.rs @@ -14,8 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use nostr::event::EventId; + use super::CliOptions; -use crate::error::N34Result; +use crate::{cli::types::NostrEvent, error::N34Result}; /// A trait defining the interface for command runners in the CLI. pub trait CommandRunner { @@ -29,3 +31,12 @@ pub trait CommandRunner { /// Executes the command and returns a Result indicating success or failure. fn run(self, options: CliOptions) -> impl Future> + Send; } + +#[easy_ext::ext(VecNostrEventExt)] +impl Vec { + /// Extracts `EventId` from each `NostrEvent` and collects them into a + /// `Vec`. + pub fn into_event_ids(self) -> Vec { + self.into_iter().map(|e| e.event_id).collect() + } +} diff --git a/src/nostr_utils/mod.rs b/src/nostr_utils/mod.rs index 74724e3..b107d20 100644 --- a/src/nostr_utils/mod.rs +++ b/src/nostr_utils/mod.rs @@ -62,6 +62,7 @@ pub struct ContentDetails { } /// A client for interacting with the Nostr relays +#[derive(Clone)] pub struct NostrClient { /// The underlying Nostr client implementation pub client: Client, @@ -323,7 +324,6 @@ impl NostrClient { return Ok(PatchStatus::Closed); } - Ok(root_status) }