feat: New patch list commands to list the repo patches
Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
@@ -14,14 +14,15 @@
|
||||
// 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>.
|
||||
|
||||
use std::iter;
|
||||
use std::{iter, str::FromStr, sync::Arc};
|
||||
|
||||
use either::Either;
|
||||
use futures::future;
|
||||
use nostr::{
|
||||
event::{EventBuilder, Tag, TagKind},
|
||||
event::{Event, EventBuilder, EventId, Kind, Tag, TagKind},
|
||||
filter::Filter,
|
||||
hashes::sha1::Hash as Sha1Hash,
|
||||
nips::nip10::Marker,
|
||||
nips::{nip10::Marker, nip19::ToBech32},
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -29,15 +30,15 @@ use super::{
|
||||
patch::PatchStatus,
|
||||
types::{NaddrOrSet, NostrEvent},
|
||||
};
|
||||
use crate::{
|
||||
cli::CliOptions,
|
||||
error::{N34Error, N34Result},
|
||||
nostr_utils::traits::{GitPatchUtils, ReposUtils},
|
||||
};
|
||||
use crate::{
|
||||
cli::types::{OptionNaddrOrSetVecExt, RelayOrSetVecExt},
|
||||
nostr_utils::{NostrClient, traits::NaddrsUtils, utils},
|
||||
};
|
||||
use crate::{
|
||||
cli::{CliOptions, patch::GitPatch},
|
||||
error::{N34Error, N34Result},
|
||||
nostr_utils::traits::{GitIssueUtils, GitPatchUtils, ReposUtils},
|
||||
};
|
||||
|
||||
/// Updates the issue's status to `new_status` after validating it with
|
||||
/// `check_fn`.
|
||||
@@ -159,17 +160,7 @@ pub async fn patch_status_command(
|
||||
));
|
||||
}
|
||||
|
||||
let (root_patch, root_revision) = if patch_event.is_revision_patch() {
|
||||
(
|
||||
patch_event.root_patch_from_revision()?,
|
||||
Some(patch_event.id),
|
||||
)
|
||||
} else if patch_event.is_root_patch() {
|
||||
(patch_event.id, None)
|
||||
} else {
|
||||
return Err(N34Error::NotRootPatch);
|
||||
};
|
||||
|
||||
let (root_patch, root_revision) = get_patch_root_revision(&patch_event)?;
|
||||
let patch_status = client
|
||||
.fetch_patch_status(
|
||||
root_patch,
|
||||
@@ -260,3 +251,153 @@ pub async fn patch_status_command(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch and display patches and issues for given repositories.
|
||||
/// If `list_patches` is true, lists patches instead of issues.
|
||||
/// `limit` controls the maximum number of items to fetch.
|
||||
pub async fn list_patches_and_issues(
|
||||
options: CliOptions,
|
||||
naddrs: Option<Vec<NaddrOrSet>>,
|
||||
list_patches: bool,
|
||||
limit: usize,
|
||||
) -> N34Result<()> {
|
||||
let naddrs = utils::check_empty_naddrs(utils::naddrs_or_file(
|
||||
naddrs.flat_naddrs(&options.config.sets)?,
|
||||
&utils::nostr_address_path()?,
|
||||
)?)?;
|
||||
|
||||
let relays = options.relays.clone().flat_relays(&options.config.sets)?;
|
||||
let client = NostrClient::init(&options, &relays).await;
|
||||
client.add_relays(&naddrs.extract_relays()).await;
|
||||
|
||||
let coordinates = naddrs.clone().into_coordinates();
|
||||
let repos = client.fetch_repos(&coordinates).await?;
|
||||
let authorized_pubkeys = [naddrs.extract_owners(), repos.extract_maintainers()].concat();
|
||||
client.add_relays(&repos.extract_relays()).await;
|
||||
// This helps discover issues and their status.
|
||||
client
|
||||
.add_relays(&client.read_relays_from_users(&authorized_pubkeys).await)
|
||||
.await;
|
||||
|
||||
|
||||
let kind = if list_patches {
|
||||
Kind::GitPatch
|
||||
} else {
|
||||
Kind::GitIssue
|
||||
};
|
||||
|
||||
let mut filter = Filter::new()
|
||||
.coordinates(coordinates.iter())
|
||||
.kind(kind)
|
||||
.limit(limit);
|
||||
|
||||
if list_patches {
|
||||
filter = filter.hashtag("root");
|
||||
}
|
||||
|
||||
let arc_client = Arc::new(client);
|
||||
// Events are sorted by kind in ascending order:
|
||||
// 1630 (Open), 1631 (Resolved/Applied), 1632 (Closed), 1633 (Draft)
|
||||
let events = utils::sort_by_key(
|
||||
future::join_all(
|
||||
arc_client
|
||||
.fetch_events(filter)
|
||||
.await?
|
||||
.into_iter()
|
||||
.take(limit)
|
||||
.map(|event| {
|
||||
let c = arc_client.clone();
|
||||
let keys = authorized_pubkeys.clone();
|
||||
async move {
|
||||
let status = if list_patches {
|
||||
let (root, root_revision) = get_patch_root_revision(&event)?;
|
||||
c.fetch_patch_status(
|
||||
root,
|
||||
root_revision,
|
||||
[keys.as_slice(), &[event.pubkey]].concat(),
|
||||
)
|
||||
.await
|
||||
.map(Either::Left)?
|
||||
} else {
|
||||
c.fetch_issue_status(
|
||||
event.id,
|
||||
[keys.as_slice(), &[event.pubkey]].concat(),
|
||||
)
|
||||
.await
|
||||
.map(Either::Right)?
|
||||
};
|
||||
N34Result::Ok((event, status))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|r| r.ok()),
|
||||
|(_, status)| status.as_ref().either_into::<Kind>(),
|
||||
);
|
||||
|
||||
let lines = events
|
||||
.map(|(event, status)| format_patch_and_issue(&event, status))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let max_width = lines
|
||||
.iter()
|
||||
.map(|s| s.split_once('\n').map_or(85, |(l, _)| l.chars().count()))
|
||||
.max()
|
||||
.unwrap_or(85)
|
||||
.max(67); // length of the event id
|
||||
|
||||
println!("{}", lines.join(&format!("{}\n", "-".repeat(max_width))));
|
||||
|
||||
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<EventId>)> {
|
||||
if patch_event.is_revision_patch() {
|
||||
Ok((
|
||||
patch_event.root_patch_from_revision()?,
|
||||
Some(patch_event.id),
|
||||
))
|
||||
} else if patch_event.is_root_patch() {
|
||||
Ok((patch_event.id, None))
|
||||
} else {
|
||||
Err(N34Error::NotRootPatch)
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats an event as either a patch or an issue. For patches, extracts the
|
||||
/// subject line from the Git patch format. For issues, combines the subject
|
||||
/// with labels. The output includes status and formatted ID.
|
||||
fn format_patch_and_issue(event: &Event, status: Either<PatchStatus, IssueStatus>) -> String {
|
||||
let subject = if status.is_left() {
|
||||
GitPatch::from_str(&event.content)
|
||||
.map(|p| p.subject)
|
||||
.unwrap_or_else(|_| {
|
||||
event
|
||||
.content
|
||||
.lines()
|
||||
.find(|line| line.trim().starts_with("Subject: "))
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.trim_start_matches("Subject: ")
|
||||
.to_owned()
|
||||
})
|
||||
} else {
|
||||
let labels = event.extract_issue_labels();
|
||||
let subject = event.extract_issue_subject();
|
||||
|
||||
if labels.is_empty() {
|
||||
subject.to_owned()
|
||||
} else {
|
||||
format!(r#""{subject}" {labels}"#)
|
||||
}
|
||||
};
|
||||
format!(
|
||||
"({status}) {}\nID: {}\n",
|
||||
utils::smart_wrap(&subject, 85),
|
||||
event.id.to_bech32().expect("Infallible")
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user