feat: New patch subcommands apply,close,draft,merge and reopen to manage the patch status
Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
@@ -23,7 +23,7 @@ use std::{collections::HashSet, time::Duration};
|
||||
|
||||
use futures::future;
|
||||
use nostr::{
|
||||
event::{Event, EventId, Kind, Tag, TagStandard, Tags, UnsignedEvent},
|
||||
event::{Event, EventId, Kind, Tag, TagKind, TagStandard, Tags, UnsignedEvent},
|
||||
filter::Filter,
|
||||
key::PublicKey,
|
||||
nips::{
|
||||
@@ -39,7 +39,7 @@ use nostr_sdk::Client;
|
||||
use traits::TokenUtils;
|
||||
|
||||
use crate::{
|
||||
cli::{CliOptions, issue::IssueStatus},
|
||||
cli::{CliOptions, issue::IssueStatus, patch::PatchStatus},
|
||||
error::{N34Error, N34Result},
|
||||
};
|
||||
|
||||
@@ -239,6 +239,13 @@ impl NostrClient {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Fetch the patch by the given id. None if not found
|
||||
pub async fn fetch_patch(&self, patch_id: EventId) -> N34Result<Event> {
|
||||
self.fetch_event(Filter::new().id(patch_id).kind(Kind::GitPatch))
|
||||
.await?
|
||||
.ok_or(N34Error::CanNotFoundPatch)
|
||||
}
|
||||
|
||||
/// Returns the username for a given public key. If no username is found,
|
||||
/// falls back to a shortened version of the public key.
|
||||
pub async fn get_username(&self, user: PublicKey) -> String {
|
||||
@@ -279,6 +286,69 @@ impl NostrClient {
|
||||
.unwrap_or_else(|| Ok(IssueStatus::Open))
|
||||
}
|
||||
|
||||
/// Gets the status of a patch. If it's a revision patch, checks if it's
|
||||
/// closed when the root patch is already merged/applied but doesn't
|
||||
/// reference this revision. Defaults to Open status if no status event
|
||||
/// is found.
|
||||
pub async fn fetch_patch_status(
|
||||
&self,
|
||||
root_patch: EventId,
|
||||
root_revision: Option<EventId>,
|
||||
authorized_pubkeys: Vec<PublicKey>,
|
||||
) -> N34Result<PatchStatus> {
|
||||
let (root_status, event_tags) = self
|
||||
.fetch_events(
|
||||
Filter::new()
|
||||
.event(root_patch)
|
||||
.kinds([
|
||||
Kind::GitStatusOpen,
|
||||
Kind::GitStatusApplied,
|
||||
Kind::GitStatusClosed,
|
||||
Kind::GitStatusDraft,
|
||||
])
|
||||
.authors(utils::dedup(authorized_pubkeys.into_iter())),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.max_by_key(|e| e.created_at)
|
||||
.map(|status| N34Result::Ok((PatchStatus::try_from(status.kind)?, status.tags)))
|
||||
.unwrap_or_else(|| Ok((PatchStatus::Open, Tags::new())))?;
|
||||
|
||||
if let Some(revision_id) = root_revision {
|
||||
if root_status.is_merged_or_applied()
|
||||
&& !event_tags
|
||||
.filter(TagKind::e())
|
||||
.any(|t| t.is_reply() && t.content().is_some_and(|c| c == revision_id.to_hex()))
|
||||
{
|
||||
return Ok(PatchStatus::Closed);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(root_status)
|
||||
}
|
||||
|
||||
pub async fn fetch_patch_series(
|
||||
&self,
|
||||
root_patch_id: EventId,
|
||||
root_patch_author: PublicKey,
|
||||
) -> N34Result<Vec<Event>> {
|
||||
Ok(self
|
||||
.fetch_events(
|
||||
Filter::new()
|
||||
.kind(Kind::GitPatch)
|
||||
.author(root_patch_author)
|
||||
.event(root_patch_id),
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|e| {
|
||||
e.tags.iter().any(|t| {
|
||||
t.is_root() && t.content().is_some_and(|c| c == root_patch_id.to_hex())
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Finds the root issue or patch for a given event. If the event is already
|
||||
/// a root (issue/patch), returns it directly. For comments, follows
|
||||
/// parent/root references until finding the root or failing. Returns
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use nostr::{
|
||||
event::{EventBuilder, EventId, Tag, TagKind, TagStandard, Tags},
|
||||
event::{Event, EventBuilder, EventId, Kind, Tag, TagKind, TagStandard, Tags},
|
||||
key::PublicKey,
|
||||
nips::{
|
||||
nip01::Coordinate,
|
||||
@@ -216,4 +216,43 @@ impl Vec<GitRepositoryAnnouncement> {
|
||||
self.iter().flat_map(|r| r.maintainers.clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility functions for working with patch events
|
||||
#[easy_ext::ext(GitPatchUtils)]
|
||||
impl Event {
|
||||
/// Returns whether the patch is a root or not
|
||||
pub fn is_root_patch(&self) -> bool {
|
||||
self.kind == Kind::GitPatch
|
||||
&& self
|
||||
.tags
|
||||
.filter(TagKind::t())
|
||||
.any(|t| t.content() == Some("root"))
|
||||
}
|
||||
|
||||
/// Returns whether the patch is patch-revision or not
|
||||
pub fn is_revision_patch(&self) -> bool {
|
||||
self.kind == Kind::GitPatch
|
||||
&& self
|
||||
.tags
|
||||
.filter(TagKind::t())
|
||||
.any(|t| t.content() == Some("root-revision"))
|
||||
}
|
||||
|
||||
/// Gets the root patch ID from a patch-revision event by finding the `e`
|
||||
/// tag that replies to it. Fails if no such tag is found or if the tag
|
||||
/// contains an invalid event ID.
|
||||
pub fn root_patch_from_revision(&self) -> N34Result<EventId> {
|
||||
self.tags
|
||||
.iter()
|
||||
.find(|tag| tag.is_reply())
|
||||
.ok_or_else(|| {
|
||||
N34Error::InvalidEvent(
|
||||
"A patch revision without `e`-reply to the root patch".to_owned(),
|
||||
)
|
||||
})?
|
||||
.content()
|
||||
.ok_or_else(|| N34Error::InvalidEvent("`e` tag without an event".to_owned()))?
|
||||
.parse()
|
||||
.map_err(|err| N34Error::InvalidEvent(format!("Invalid event ID in `e` tag: {err}")))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user