upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bin/ngit/sub_commands/issue_create.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-03-04 14:28:38 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-03-04 14:50:01 +0000
commitb3b1a949463d8e18622519866ecee3f1b65cc888 (patch)
tree9c728adb75fb18cb84e8d13efbbbd5e90231ec2f /src/bin/ngit/sub_commands/issue_create.rs
parent6d9b0cc8fff65447849d0d55db177dcdff315c48 (diff)
restructure CLI around ngit pr/issue subcommand groups
Introduce ngit pr subcommand group (list, view, checkout, apply, send, close, reopen, ready, comment, merge) replacing the former top-level ngit list/checkout/apply commands. ngit send is kept at the top level. Expand ngit issue with view, create, close, reopen, comment subcommands. Status changes (close/reopen/ready) are gated to the PR/issue author or a repository maintainer. ngit pr merge is maintainer-only and publishes a GitStatusApplied event immediately after the git merge.
Diffstat (limited to 'src/bin/ngit/sub_commands/issue_create.rs')
-rw-r--r--src/bin/ngit/sub_commands/issue_create.rs122
1 files changed, 122 insertions, 0 deletions
diff --git a/src/bin/ngit/sub_commands/issue_create.rs b/src/bin/ngit/sub_commands/issue_create.rs
new file mode 100644
index 0000000..0c4b677
--- /dev/null
+++ b/src/bin/ngit/sub_commands/issue_create.rs
@@ -0,0 +1,122 @@
1use anyhow::{Context, Result, bail};
2use ngit::client::{Params, send_events, sign_event};
3use nostr::{EventBuilder, Tag, TagStandard, ToBech32, nips::nip19::Nip19Event};
4use nostr_sdk::Kind;
5
6use crate::{
7 client::{Client, Connect, fetching_with_report, get_repo_ref_from_cache},
8 git::{Repo, RepoActions},
9 login,
10 repo_ref::get_repo_coordinates_when_remote_unknown,
11};
12
13pub async fn launch(
14 title: Option<String>,
15 body: Option<String>,
16 labels: Vec<String>,
17) -> Result<()> {
18 let git_repo = Repo::discover().context("failed to find a git repository")?;
19 let git_repo_path = git_repo.get_path()?;
20
21 let client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo)));
22 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?;
23
24 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
25
26 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?;
27
28 // Resolve title — required
29 let title = match title {
30 Some(t) if !t.trim().is_empty() => t,
31 _ => bail!("--title is required to create an issue"),
32 };
33
34 // Body defaults to empty string if not provided
35 let body = body.unwrap_or_default();
36
37 // Login
38 let (signer, user_ref, _) =
39 login::login_or_signup(&Some(&git_repo), &None, &None, Some(&client), true).await?;
40
41 // Build NIP-34 GitIssue event (kind 1621)
42 // Tags:
43 // - `a` coordinate tags for each maintainer's repo announcement
44 // - `subject` — issue title
45 // - `t` — hashtag labels
46 // - `alt` — human-readable summary
47 let mut tags: Vec<Tag> = vec![];
48
49 // Repo coordinate tags (one per maintainer)
50 for coord in repo_ref.coordinates() {
51 tags.push(Tag::from_standardized(TagStandard::Coordinate {
52 coordinate: coord.coordinate.clone(),
53 relay_url: coord.relays.first().cloned(),
54 uppercase: false,
55 }));
56 }
57
58 // Subject (title)
59 tags.push(Tag::parse(vec!["subject".to_string(), title.clone()])?);
60
61 // Hashtag labels
62 for label in &labels {
63 tags.push(Tag::hashtag(label));
64 }
65
66 // Alt text
67 tags.push(Tag::custom(
68 nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")),
69 vec![format!("git issue: {title}")],
70 ));
71
72 // Maintainer p-tags (so they get notified)
73 for pk in &repo_ref.maintainers {
74 tags.push(Tag::public_key(*pk));
75 }
76
77 let issue_event = sign_event(
78 EventBuilder::new(Kind::GitIssue, body).tags(tags),
79 &signer,
80 "create issue".to_string(),
81 )
82 .await?;
83
84 let event_id = issue_event.id;
85
86 let mut client = client;
87 client.set_signer(signer).await;
88
89 send_events(
90 &client,
91 Some(git_repo_path),
92 vec![issue_event],
93 user_ref.relays.write(),
94 repo_ref.relays.clone(),
95 true,
96 false,
97 )
98 .await?;
99
100 let event_bech32 = if let Some(relay) = repo_ref.relays.first() {
101 Nip19Event {
102 event_id,
103 relays: vec![relay.clone()],
104 author: None,
105 kind: None,
106 }
107 .to_bech32()?
108 } else {
109 event_id.to_bech32()?
110 };
111
112 println!("issue created: {event_id}");
113 let dim = console::Style::new().color256(247);
114 println!(
115 "{}",
116 dim.apply_to(format!(
117 "view in gitworkshop.dev: https://gitworkshop.dev/{}",
118 &event_bech32,
119 ))
120 );
121 Ok(())
122}