upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-12-09 09:34:08 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-12-10 11:55:01 +0000
commit6d3a4eb870cd344b11ccda13e1339584ed4e4d17 (patch)
tree37cb5787bb62c519a235d5a8b0c5c0985d8b4977
parentf0d0e1ba1cba11d3a98a5ab0c7f1dc72b6bc4e17 (diff)
feat(NostrUrlDecoded) add nip05 support
replace `NostrUrlDecoded::from_str` with `NostrUrlDecoded::parse_and_resolve` store nip05 pubkey mapping in git cache
-rw-r--r--src/bin/git_remote_nostr/main.rs12
-rw-r--r--src/bin/ngit/sub_commands/init.rs10
-rw-r--r--src/lib/git/nostr_url.rs81
-rw-r--r--src/lib/repo_ref.rs16
4 files changed, 96 insertions, 23 deletions
diff --git a/src/bin/git_remote_nostr/main.rs b/src/bin/git_remote_nostr/main.rs
index 8e12d68..84327d7 100644
--- a/src/bin/git_remote_nostr/main.rs
+++ b/src/bin/git_remote_nostr/main.rs
@@ -9,7 +9,6 @@ use std::{
9 collections::HashSet, 9 collections::HashSet,
10 env, io, 10 env, io,
11 path::{Path, PathBuf}, 11 path::{Path, PathBuf},
12 str::FromStr,
13}; 12};
14 13
15use anyhow::{bail, Context, Result}; 14use anyhow::{bail, Context, Result};
@@ -28,7 +27,7 @@ mod utils;
28 27
29#[tokio::main] 28#[tokio::main]
30async fn main() -> Result<()> { 29async fn main() -> Result<()> {
31 let Some((decoded_nostr_url, git_repo)) = process_args()? else { 30 let Some((decoded_nostr_url, git_repo)) = process_args().await? else {
32 return Ok(()); 31 return Ok(());
33 }; 32 };
34 33
@@ -109,7 +108,7 @@ async fn main() -> Result<()> {
109 } 108 }
110} 109}
111 110
112fn process_args() -> Result<Option<(NostrUrlDecoded, Repo)>> { 111async fn process_args() -> Result<Option<(NostrUrlDecoded, Repo)>> {
113 let args = env::args(); 112 let args = env::args();
114 let args = args.skip(1).take(2).collect::<Vec<_>>(); 113 let args = args.skip(1).take(2).collect::<Vec<_>>();
115 114
@@ -135,13 +134,14 @@ fn process_args() -> Result<Option<(NostrUrlDecoded, Repo)>> {
135 return Ok(None); 134 return Ok(None);
136 }; 135 };
137 136
138 let decoded_nostr_url =
139 NostrUrlDecoded::from_str(nostr_remote_url).context("invalid nostr url")?;
140
141 let git_repo = Repo::from_path(&PathBuf::from( 137 let git_repo = Repo::from_path(&PathBuf::from(
142 std::env::var("GIT_DIR").context("git should set GIT_DIR when remote helper is called")?, 138 std::env::var("GIT_DIR").context("git should set GIT_DIR when remote helper is called")?,
143 ))?; 139 ))?;
144 140
141 let decoded_nostr_url = NostrUrlDecoded::parse_and_resolve(nostr_remote_url, &Some(&git_repo))
142 .await
143 .context("invalid nostr url")?;
144
145 Ok(Some((decoded_nostr_url, git_repo))) 145 Ok(Some((decoded_nostr_url, git_repo)))
146} 146}
147 147
diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs
index 6fc1ec4..c9c8873 100644
--- a/src/bin/ngit/sub_commands/init.rs
+++ b/src/bin/ngit/sub_commands/init.rs
@@ -1,4 +1,4 @@
1use std::{collections::HashMap, str::FromStr}; 1use std::collections::HashMap;
2 2
3use anyhow::{Context, Result}; 3use anyhow::{Context, Result};
4use console::Style; 4use console::Style;
@@ -442,7 +442,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
442 .map(std::string::ToString::to_string) 442 .map(std::string::ToString::to_string)
443 .collect::<Vec<String>>(); 443 .collect::<Vec<String>>();
444 444
445 prompt_to_set_nostr_url_as_origin(&repo_ref, &git_repo)?; 445 prompt_to_set_nostr_url_as_origin(&repo_ref, &git_repo).await?;
446 446
447 // TODO: if no state event exists and there is currently a remote called 447 // TODO: if no state event exists and there is currently a remote called
448 // "origin", automtically push rather than waiting for the next commit 448 // "origin", automtically push rather than waiting for the next commit
@@ -483,7 +483,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
483 Ok(()) 483 Ok(())
484} 484}
485 485
486fn prompt_to_set_nostr_url_as_origin(repo_ref: &RepoRef, git_repo: &Repo) -> Result<()> { 486async fn prompt_to_set_nostr_url_as_origin(repo_ref: &RepoRef, git_repo: &Repo) -> Result<()> {
487 println!( 487 println!(
488 "starting from your next commit, when you `git push` to a remote that uses your nostr url, it will store your repository state on nostr and update the state of the git server(s) you just listed." 488 "starting from your next commit, when you `git push` to a remote that uses your nostr url, it will store your repository state on nostr and update the state of the git server(s) you just listed."
489 ); 489 );
@@ -493,7 +493,9 @@ fn prompt_to_set_nostr_url_as_origin(repo_ref: &RepoRef, git_repo: &Repo) -> Res
493 493
494 if let Ok(origin_remote) = git_repo.git_repo.find_remote("origin") { 494 if let Ok(origin_remote) = git_repo.git_repo.find_remote("origin") {
495 if let Some(origin_url) = origin_remote.url() { 495 if let Some(origin_url) = origin_remote.url() {
496 if let Ok(nostr_url) = NostrUrlDecoded::from_str(origin_url) { 496 if let Ok(nostr_url) =
497 NostrUrlDecoded::parse_and_resolve(origin_url, &Some(git_repo)).await
498 {
497 if nostr_url.coordinate.identifier == repo_ref.identifier { 499 if nostr_url.coordinate.identifier == repo_ref.identifier {
498 if nostr_url.coordinate.public_key == repo_ref.trusted_maintainer { 500 if nostr_url.coordinate.public_key == repo_ref.trusted_maintainer {
499 return Ok(()); 501 return Ok(());
diff --git a/src/lib/git/nostr_url.rs b/src/lib/git/nostr_url.rs
index c26bb2e..ac57538 100644
--- a/src/lib/git/nostr_url.rs
+++ b/src/lib/git/nostr_url.rs
@@ -2,9 +2,11 @@ use core::fmt;
2use std::str::FromStr; 2use std::str::FromStr;
3 3
4use anyhow::{anyhow, bail, Context, Error, Result}; 4use anyhow::{anyhow, bail, Context, Error, Result};
5use nostr::nips::nip01::Coordinate; 5use nostr::nips::{nip01::Coordinate, nip05};
6use nostr_sdk::{PublicKey, RelayUrl, ToBech32, Url}; 6use nostr_sdk::{PublicKey, RelayUrl, ToBech32, Url};
7 7
8use super::{get_git_config_item, save_git_config_item, Repo};
9
8#[derive(Debug, PartialEq, Default, Clone)] 10#[derive(Debug, PartialEq, Default, Clone)]
9pub enum ServerProtocol { 11pub enum ServerProtocol {
10 Ssh, 12 Ssh,
@@ -59,6 +61,7 @@ pub struct NostrUrlDecoded {
59 pub coordinate: Coordinate, 61 pub coordinate: Coordinate,
60 pub protocol: Option<ServerProtocol>, 62 pub protocol: Option<ServerProtocol>,
61 pub user: Option<String>, 63 pub user: Option<String>,
64 pub nip05: Option<String>,
62} 65}
63 66
64impl fmt::Display for NostrUrlDecoded { 67impl fmt::Display for NostrUrlDecoded {
@@ -89,10 +92,8 @@ impl fmt::Display for NostrUrlDecoded {
89 92
90static INCORRECT_NOSTR_URL_FORMAT_ERROR: &str = "incorrect nostr git url format. try nostr://naddr123 or nostr://npub123/my-repo or nostr://ssh/npub123/relay.damus.io/my-repo"; 93static INCORRECT_NOSTR_URL_FORMAT_ERROR: &str = "incorrect nostr git url format. try nostr://naddr123 or nostr://npub123/my-repo or nostr://ssh/npub123/relay.damus.io/my-repo";
91 94
92impl std::str::FromStr for NostrUrlDecoded { 95impl NostrUrlDecoded {
93 type Err = anyhow::Error; 96 pub async fn parse_and_resolve(url: &str, git_repo: &Option<&Repo>) -> Result<Self> {
94
95 fn from_str(url: &str) -> Result<Self> {
96 let mut protocol = None; 97 let mut protocol = None;
97 let mut user = None; 98 let mut user = None;
98 let mut relays = vec![]; 99 let mut relays = vec![];
@@ -154,6 +155,7 @@ impl std::str::FromStr for NostrUrlDecoded {
154 } 155 }
155 // extract naddr npub/<optional-relays>/identifer 156 // extract naddr npub/<optional-relays>/identifer
156 let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?; 157 let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;
158 let mut nip05 = None;
157 // naddr used 159 // naddr used
158 let coordinate = if let Ok(coordinate) = Coordinate::parse(part) { 160 let coordinate = if let Ok(coordinate) = Coordinate::parse(part) {
159 if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) { 161 if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) {
@@ -161,8 +163,9 @@ impl std::str::FromStr for NostrUrlDecoded {
161 } else { 163 } else {
162 bail!("naddr doesnt point to a git repository announcement"); 164 bail!("naddr doesnt point to a git repository announcement");
163 } 165 }
164 // npub/<optional-relays>/identifer used 166 // <npub|nip05_address>/<optional-relays>/identifer used
165 } else if let Ok(public_key) = PublicKey::parse(part) { 167 } else {
168 let npub_or_nip05 = part.to_owned();
166 parts.remove(0); 169 parts.remove(0);
167 let identifier = parts 170 let identifier = parts
168 .pop() 171 .pop()
@@ -179,14 +182,41 @@ impl std::str::FromStr for NostrUrlDecoded {
179 RelayUrl::parse(&decoded).context("could not parse relays in nostr git url")?; 182 RelayUrl::parse(&decoded).context("could not parse relays in nostr git url")?;
180 relays.push(url); 183 relays.push(url);
181 } 184 }
185 let public_key = match PublicKey::parse(npub_or_nip05) {
186 Ok(public_key) => public_key,
187 Err(_) => {
188 nip05 = Some(npub_or_nip05.to_string());
189 if let Ok(public_key) =
190 resolve_nip05_from_git_config_cache(npub_or_nip05, git_repo)
191 {
192 public_key
193 } else {
194 // TODO eprint loading message
195 let res = nip05::profile(npub_or_nip05, None)
196 .await
197 .context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;
198 // TODO clear loading message
199 nip05 = Some(npub_or_nip05.to_string());
200 let _ = save_nip05_to_git_config_cache(
201 npub_or_nip05,
202 &res.public_key,
203 git_repo,
204 );
205 if relays.is_empty() {
206 for r in res.relays {
207 relays.push(r);
208 }
209 }
210 res.public_key
211 }
212 }
213 };
182 Coordinate { 214 Coordinate {
183 identifier, 215 identifier,
184 public_key, 216 public_key,
185 kind: nostr_sdk::Kind::GitRepoAnnouncement, 217 kind: nostr_sdk::Kind::GitRepoAnnouncement,
186 relays, 218 relays,
187 } 219 }
188 } else {
189 bail!(INCORRECT_NOSTR_URL_FORMAT_ERROR);
190 }; 220 };
191 221
192 Ok(Self { 222 Ok(Self {
@@ -194,10 +224,43 @@ impl std::str::FromStr for NostrUrlDecoded {
194 coordinate, 224 coordinate,
195 protocol, 225 protocol,
196 user, 226 user,
227 nip05,
197 }) 228 })
198 } 229 }
199} 230}
200 231
232fn resolve_nip05_from_git_config_cache(nip05: &str, git_repo: &Option<&Repo>) -> Result<PublicKey> {
233 let stored_value = get_git_config_item(
234 git_repo,
235 &format!("nostr.nip05.{}", urlencoding::encode(nip05)),
236 )?
237 .context("not in cache")?;
238 PublicKey::parse(stored_value)
239 .context("stored nip05 resolution value did not parse as public key")
240}
241
242fn save_nip05_to_git_config_cache(
243 nip05: &str,
244 public_key: &PublicKey,
245 git_repo: &Option<&Repo>,
246) -> Result<()> {
247 if save_git_config_item(
248 git_repo,
249 &format!("nostr.nip05.{}", urlencoding::encode(nip05)),
250 &public_key.to_bech32()?,
251 )
252 .is_err()
253 {
254 save_git_config_item(
255 &None,
256 &format!("nostr.nip05.{}", urlencoding::encode(nip05)),
257 &public_key.to_bech32()?,
258 )
259 } else {
260 Ok(())
261 }
262}
263
201#[derive(Debug, PartialEq, Default)] 264#[derive(Debug, PartialEq, Default)]
202pub struct CloneUrl { 265pub struct CloneUrl {
203 original_string: String, 266 original_string: String,
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs
index 1b25ccf..089befc 100644
--- a/src/lib/repo_ref.rs
+++ b/src/lib/repo_ref.rs
@@ -41,6 +41,7 @@ impl TryFrom<(nostr::Event, Option<PublicKey>)> for RepoRef {
41 type Error = anyhow::Error; 41 type Error = anyhow::Error;
42 42
43 fn try_from((event, trusted_maintainer): (nostr::Event, Option<PublicKey>)) -> Result<Self> { 43 fn try_from((event, trusted_maintainer): (nostr::Event, Option<PublicKey>)) -> Result<Self> {
44 // TODO: turn trusted maintainer into NostrUrlDecoded
44 if !event.kind.eq(&Kind::GitRepoAnnouncement) { 45 if !event.kind.eq(&Kind::GitRepoAnnouncement) {
45 bail!("incorrect kind"); 46 bail!("incorrect kind");
46 } 47 }
@@ -239,6 +240,7 @@ impl RepoRef {
239 coordinate: self.coordinate_with_hint(), 240 coordinate: self.coordinate_with_hint(),
240 protocol: None, 241 protocol: None,
241 user: None, 242 user: None,
243 nip05: None, // TODO: if nip05 for pubkey saved in local git config use it.
242 } 244 }
243 ) 245 )
244 } 246 }
@@ -259,7 +261,7 @@ pub async fn get_repo_coordinates_when_remote_unknown(
259pub async fn try_and_get_repo_coordinates_when_remote_unknown( 261pub async fn try_and_get_repo_coordinates_when_remote_unknown(
260 git_repo: &Repo, 262 git_repo: &Repo,
261) -> Result<Coordinate> { 263) -> Result<Coordinate> {
262 let remote_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo)?; 264 let remote_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo).await?;
263 if remote_coordinates.is_empty() { 265 if remote_coordinates.is_empty() {
264 if let Ok(c) = get_repo_coordinates_from_git_config(git_repo) { 266 if let Ok(c) = get_repo_coordinates_from_git_config(git_repo) {
265 Ok(c) 267 Ok(c)
@@ -327,11 +329,15 @@ fn get_repo_coordinates_from_git_config(git_repo: &Repo) -> Result<Coordinate> {
327 .context("git config item \"nostr.repo\" is not an naddr") 329 .context("git config item \"nostr.repo\" is not an naddr")
328} 330}
329 331
330fn get_repo_coordinates_from_nostr_remotes(git_repo: &Repo) -> Result<HashMap<String, Coordinate>> { 332async fn get_repo_coordinates_from_nostr_remotes(
333 git_repo: &Repo,
334) -> Result<HashMap<String, Coordinate>> {
331 let mut repo_coordinates = HashMap::new(); 335 let mut repo_coordinates = HashMap::new();
332 for remote_name in git_repo.git_repo.remotes()?.iter().flatten() { 336 for remote_name in git_repo.git_repo.remotes()?.iter().flatten() {
333 if let Some(remote_url) = git_repo.git_repo.find_remote(remote_name)?.url() { 337 if let Some(remote_url) = git_repo.git_repo.find_remote(remote_name)?.url() {
334 if let Ok(nostr_url_decoded) = NostrUrlDecoded::from_str(remote_url) { 338 if let Ok(nostr_url_decoded) =
339 NostrUrlDecoded::parse_and_resolve(remote_url, &Some(git_repo)).await
340 {
335 repo_coordinates.insert(remote_name.to_string(), nostr_url_decoded.coordinate); 341 repo_coordinates.insert(remote_name.to_string(), nostr_url_decoded.coordinate);
336 } 342 }
337 } 343 }
@@ -383,7 +389,9 @@ async fn get_repo_coordinate_from_user_prompt(
383 .input(PromptInputParms::default().with_prompt("nostr repository"))?; 389 .input(PromptInputParms::default().with_prompt("nostr repository"))?;
384 let coordinate = if let Ok(c) = Coordinate::parse(&input) { 390 let coordinate = if let Ok(c) = Coordinate::parse(&input) {
385 c 391 c
386 } else if let Ok(nostr_url) = NostrUrlDecoded::from_str(&input) { 392 } else if let Ok(nostr_url) =
393 NostrUrlDecoded::parse_and_resolve(&input, &Some(git_repo)).await
394 {
387 nostr_url.coordinate 395 nostr_url.coordinate
388 } else { 396 } else {
389 eprintln!("not a valid naddr or git nostr remote URL starting nostr://"); 397 eprintln!("not a valid naddr or git nostr remote URL starting nostr://");