feat: New flag to patch apply and merge to mention patches
Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
@@ -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
|
- Support signing using NIP-46 bunker - by Awiteb
|
||||||
- Keyring the secret key `n34 config keyring --enable` - 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
|
### Dependencies
|
||||||
|
|
||||||
- Add `keyring`, `nostr-connect`, `nostr-keyring` and `url` to the dependencies - by Awiteb
|
- 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
|
## [0.3.0] - 2025-07-05
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Arguments:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--repo <NADDR-NIP05-OR-SET> Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`
|
--repo <NADDR-NIP05-OR-SET> Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`
|
||||||
|
--patches <PATCH-EVENT-ID> 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
|
Creates a kind `1631` event (Applied/Merged status) for the specified patch. The
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Arguments:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--repo <NADDR-NIP05-OR-SET> Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`
|
--repo <NADDR-NIP05-OR-SET> Repository address in `naddr` format (`naddr1...`), NIP-05 format (`4rs.nl/n34` or `_@4rs.nl/n34`), or a set name like `kernel`
|
||||||
|
--patches <PATCH-EVENT-ID> 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
|
Creates a kind `1631` event (Applied/Merged status) for the specified patch. The
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use super::PatchStatus;
|
|||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
cli::{
|
||||||
CliOptions,
|
CliOptions,
|
||||||
traits::CommandRunner,
|
traits::{CommandRunner, VecNostrEventExt},
|
||||||
types::{NaddrOrSet, NostrEvent},
|
types::{NaddrOrSet, NostrEvent},
|
||||||
},
|
},
|
||||||
error::{N34Error, N34Result},
|
error::{N34Error, N34Result},
|
||||||
@@ -38,6 +38,10 @@ pub struct ApplyArgs {
|
|||||||
/// The open patch id to apply it. Must be orignal root patch or
|
/// The open patch id to apply it. Must be orignal root patch or
|
||||||
/// revision root
|
/// revision root
|
||||||
patch_id: NostrEvent,
|
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<NostrEvent>,
|
||||||
/// The applied commits
|
/// The applied commits
|
||||||
#[arg(num_args = 1..)]
|
#[arg(num_args = 1..)]
|
||||||
applied_commits: Vec<Sha1Hash>,
|
applied_commits: Vec<Sha1Hash>,
|
||||||
@@ -51,6 +55,7 @@ impl CommandRunner for ApplyArgs {
|
|||||||
self.naddrs,
|
self.naddrs,
|
||||||
PatchStatus::MergedApplied,
|
PatchStatus::MergedApplied,
|
||||||
Some(either::Either::Right(self.applied_commits)),
|
Some(either::Either::Right(self.applied_commits)),
|
||||||
|
self.applied_patches.into_event_ids(),
|
||||||
|patch_status| {
|
|patch_status| {
|
||||||
if patch_status.is_merged_or_applied() {
|
if patch_status.is_merged_or_applied() {
|
||||||
return Err(N34Error::InvalidStatus(
|
return Err(N34Error::InvalidStatus(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ impl CommandRunner for CloseArgs {
|
|||||||
self.naddrs,
|
self.naddrs,
|
||||||
PatchStatus::Closed,
|
PatchStatus::Closed,
|
||||||
None,
|
None,
|
||||||
|
Vec::new(),
|
||||||
|patch_status| {
|
|patch_status| {
|
||||||
if patch_status.is_closed() {
|
if patch_status.is_closed() {
|
||||||
return Err(N34Error::InvalidStatus(
|
return Err(N34Error::InvalidStatus(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ impl CommandRunner for DraftArgs {
|
|||||||
self.naddrs,
|
self.naddrs,
|
||||||
PatchStatus::Draft,
|
PatchStatus::Draft,
|
||||||
None,
|
None,
|
||||||
|
Vec::new(),
|
||||||
|patch_status| {
|
|patch_status| {
|
||||||
if patch_status.is_drafted() {
|
if patch_status.is_drafted() {
|
||||||
return Err(N34Error::InvalidStatus(
|
return Err(N34Error::InvalidStatus(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use super::PatchStatus;
|
|||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
cli::{
|
||||||
CliOptions,
|
CliOptions,
|
||||||
traits::CommandRunner,
|
traits::{CommandRunner, VecNostrEventExt},
|
||||||
types::{NaddrOrSet, NostrEvent},
|
types::{NaddrOrSet, NostrEvent},
|
||||||
},
|
},
|
||||||
error::{N34Error, N34Result},
|
error::{N34Error, N34Result},
|
||||||
@@ -34,12 +34,16 @@ pub struct MergeArgs {
|
|||||||
///
|
///
|
||||||
/// If omitted, looks for a `nostr-address` file.
|
/// If omitted, looks for a `nostr-address` file.
|
||||||
#[arg(value_name = "NADDR-NIP05-OR-SET", long = "repo")]
|
#[arg(value_name = "NADDR-NIP05-OR-SET", long = "repo")]
|
||||||
naddrs: Option<Vec<NaddrOrSet>>,
|
naddrs: Option<Vec<NaddrOrSet>>,
|
||||||
/// The open patch id to merge it. Must be orignal root patch or
|
/// The open patch id to merge it. Must be orignal root patch or
|
||||||
/// revision root
|
/// 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<NostrEvent>,
|
||||||
/// The merge commit id
|
/// The merge commit id
|
||||||
merge_commit: Sha1Hash,
|
merge_commit: Sha1Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandRunner for MergeArgs {
|
impl CommandRunner for MergeArgs {
|
||||||
@@ -50,6 +54,7 @@ impl CommandRunner for MergeArgs {
|
|||||||
self.naddrs,
|
self.naddrs,
|
||||||
PatchStatus::MergedApplied,
|
PatchStatus::MergedApplied,
|
||||||
Some(either::Either::Left(self.merge_commit)),
|
Some(either::Either::Left(self.merge_commit)),
|
||||||
|
self.merged_patches.into_event_ids(),
|
||||||
|patch_status| {
|
|patch_status| {
|
||||||
if patch_status.is_merged_or_applied() {
|
if patch_status.is_merged_or_applied() {
|
||||||
return Err(N34Error::InvalidStatus(
|
return Err(N34Error::InvalidStatus(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ impl CommandRunner for ReopenArgs {
|
|||||||
self.naddrs,
|
self.naddrs,
|
||||||
PatchStatus::Open,
|
PatchStatus::Open,
|
||||||
None,
|
None,
|
||||||
|
Vec::new(),
|
||||||
|patch_status| {
|
|patch_status| {
|
||||||
if patch_status.is_open() {
|
if patch_status.is_open() {
|
||||||
return Err(N34Error::InvalidStatus(
|
return Err(N34Error::InvalidStatus(
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use nostr::{
|
|||||||
filter::Filter,
|
filter::Filter,
|
||||||
hashes::sha1::Hash as Sha1Hash,
|
hashes::sha1::Hash as Sha1Hash,
|
||||||
nips::{nip10::Marker, nip19::ToBech32},
|
nips::{nip10::Marker, nip19::ToBech32},
|
||||||
|
types::RelayUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -130,6 +131,7 @@ pub async fn patch_status_command(
|
|||||||
naddrs: Option<Vec<NaddrOrSet>>,
|
naddrs: Option<Vec<NaddrOrSet>>,
|
||||||
new_status: PatchStatus,
|
new_status: PatchStatus,
|
||||||
merge_or_applied_commits: Option<Either<Sha1Hash, Vec<Sha1Hash>>>,
|
merge_or_applied_commits: Option<Either<Sha1Hash, Vec<Sha1Hash>>>,
|
||||||
|
merge_or_applied_patches: Vec<EventId>,
|
||||||
check_fn: impl FnOnce(&PatchStatus) -> N34Result<()>,
|
check_fn: impl FnOnce(&PatchStatus) -> N34Result<()>,
|
||||||
) -> N34Result<()> {
|
) -> N34Result<()> {
|
||||||
let user_pkey = options.pubkey().await?;
|
let user_pkey = options.pubkey().await?;
|
||||||
@@ -209,22 +211,20 @@ pub async fn patch_status_command(
|
|||||||
.tags(commits.into_iter().map(Tag::reference));
|
.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(
|
status_builder = status_builder.tag(utils::event_reply_tag(
|
||||||
&root_revision,
|
&root_revision,
|
||||||
relay_hint.as_ref(),
|
relay_hint.as_ref(),
|
||||||
Marker::Reply,
|
Marker::Reply,
|
||||||
));
|
));
|
||||||
root_revision
|
}
|
||||||
} else {
|
|
||||||
root_patch
|
if !merge_or_applied_patches.is_empty() {
|
||||||
};
|
status_builder = status_builder.tags(
|
||||||
let patches = client.fetch_patch_series(root, patch_event.pubkey).await?;
|
build_patches_quote(client.clone(), relay_hint.clone(), merge_or_applied_patches)
|
||||||
status_builder = status_builder.tags(
|
.await,
|
||||||
patches
|
);
|
||||||
.into_iter()
|
}
|
||||||
.map(|p| utils::event_reply_tag(&p.id, relay_hint.as_ref(), Marker::Mention)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let status_event = status_builder.dedup_tags().build(user_pkey);
|
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)
|
.add_relays(&client.read_relays_from_users(&authorized_pubkeys).await)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
||||||
let kind = if list_patches {
|
let kind = if list_patches {
|
||||||
Kind::GitPatch
|
Kind::GitPatch
|
||||||
} else {
|
} else {
|
||||||
@@ -352,7 +351,6 @@ pub async fn list_patches_and_issues(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Returns a tuple of (root_id, patch_id) if this is a valid root or revision
|
/// Returns a tuple of (root_id, patch_id) if this is a valid root or revision
|
||||||
/// patch.
|
/// patch.
|
||||||
fn get_patch_root_revision(patch_event: &Event) -> N34Result<(EventId, Option<EventId>)> {
|
fn get_patch_root_revision(patch_event: &Event) -> N34Result<(EventId, Option<EventId>)> {
|
||||||
@@ -401,3 +399,40 @@ fn format_patch_and_issue(event: &Event, status: Either<PatchStatus, IssueStatus
|
|||||||
event.id.to_bech32().expect("Infallible")
|
event.id.to_bech32().expect("Infallible")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a list of tags for quoting patches in merge/applied status events.
|
||||||
|
async fn build_patches_quote(
|
||||||
|
client: NostrClient,
|
||||||
|
relay_hint: Option<RelayUrl>,
|
||||||
|
patches: Vec<EventId>,
|
||||||
|
) -> Vec<Tag> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,8 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
|
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
|
||||||
|
|
||||||
|
use nostr::event::EventId;
|
||||||
|
|
||||||
use super::CliOptions;
|
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.
|
/// A trait defining the interface for command runners in the CLI.
|
||||||
pub trait CommandRunner {
|
pub trait CommandRunner {
|
||||||
@@ -29,3 +31,12 @@ pub trait CommandRunner {
|
|||||||
/// Executes the command and returns a Result indicating success or failure.
|
/// Executes the command and returns a Result indicating success or failure.
|
||||||
fn run(self, options: CliOptions) -> impl Future<Output = N34Result<()>> + Send;
|
fn run(self, options: CliOptions) -> impl Future<Output = N34Result<()>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[easy_ext::ext(VecNostrEventExt)]
|
||||||
|
impl Vec<NostrEvent> {
|
||||||
|
/// Extracts `EventId` from each `NostrEvent` and collects them into a
|
||||||
|
/// `Vec<EventId>`.
|
||||||
|
pub fn into_event_ids(self) -> Vec<EventId> {
|
||||||
|
self.into_iter().map(|e| e.event_id).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ pub struct ContentDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A client for interacting with the Nostr relays
|
/// A client for interacting with the Nostr relays
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct NostrClient {
|
pub struct NostrClient {
|
||||||
/// The underlying Nostr client implementation
|
/// The underlying Nostr client implementation
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
@@ -323,7 +324,6 @@ impl NostrClient {
|
|||||||
return Ok(PatchStatus::Closed);
|
return Ok(PatchStatus::Closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Ok(root_status)
|
Ok(root_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user