Event JSON
{
"id": "a3510f627f95705da09cc8727b1e36fa069c2163c49cb79c6fd9345ec8b36f5f",
"pubkey": "a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d",
"created_at": 1733832863,
"kind": 1617,
"tags": [
[
"a",
"30617:a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d:ngit"
],
[
"r",
"26689f97810fc656c7134c76e2a37d33b2e40ce7"
],
[
"r",
"6d3a4eb870cd344b11ccda13e1339584ed4e4d17"
],
[
"alt",
"git patch: feat(NostrUrlDecoded) add nip05 support"
],
[
"t",
"root"
],
[
"t",
"revision-root"
],
[
"e",
"ff1845c0d52fc6e177767aa2b111b3130c06e2faf57d614080043a135ecb8079",
"",
"reply"
],
[
"branch-name",
"nip05-lez(ff1845c0)"
],
[
"p",
"a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d"
],
[
"commit",
"6d3a4eb870cd344b11ccda13e1339584ed4e4d17"
],
[
"parent-commit",
"f0d0e1ba1cba11d3a98a5ab0c7f1dc72b6bc4e17"
],
[
"commit-pgp-sig",
"-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCAAdFiEEsRaN4Kb3mvfPwNXpaOFUhtc/deEFAmdYLBcACgkQaOFUhtc/\ndeEP8g//WMkihi2UTQNRACDx8wNrygNZzlBv9wDHhGLf9ktSOW1ldoXCHTc5WZQF\nbMCuPUi3dIyew+6gn5I/MaqD5kJbPnCqzDhCpkgb95EBbvDd6gvomobDuQXS82JA\nx9nFmV9SpPilzonaOEsNmRaAciniu4j/Qw36/7jRZFCkl+0p2b/78BxpmJx/wslC\nQ0IAO0QbJ9bWr3PtZjtXb6ywwaTbsel4hPcizaDIGi58YiJzRwiuCzApXy86odi5\nS0FDSqlYVsmn8opM1jv4wFXufsICzatf8G+8bIkFzUIEt3EpEAyVEkXF6TJ3BqMe\nuCO20q3rhYDmo6xVp1HGBjux8hkauq1QOFTLwlyrNwGDyhqF9atKeaq2gKYxrFhQ\nHkIxNM5I3LF/orbxVatWch61ybFDYtq5umoHGB82SPv3YsPUfEDiKO1Ho1qLcfv7\ngOfklngZnpMBob55CF+nyeJmiZh+b8o//vEgxPHGhhShWLLRvcQLXlLUpFZpuzoZ\n7WEogSm0NAySXnM+v88bYjK+GJmxrxeQFuMby947pBZoGVvevPR4qEqE4JsenXEC\nn5ziIrNMWQvuSg132slQtQ+MqYUMmK7zzrkw8P74ue/vudlnLUrFonR7jbUv5E0r\n0hiPSl5LoiXgBUekA1J270pnLj2k7NNuSMmGIQA25HzWDgj0n7E=\n=c/Nt\n-----END PGP SIGNATURE-----"
],
[
"description",
"feat(NostrUrlDecoded) add nip05 support\n\nreplace `NostrUrlDecoded::from_str` with\n`NostrUrlDecoded::parse_and_resolve`\n\nstore nip05 pubkey mapping in git cache\n"
],
[
"author",
"DanConwayDev",
"DanConwayDev@protonmail.com",
"1733736848",
"0"
],
[
"committer",
"DanConwayDev",
"DanConwayDev@protonmail.com",
"1733831701",
"0"
]
],
"content": "From 6d3a4eb870cd344b11ccda13e1339584ed4e4d17 Mon Sep 17 00:00:00 2001\nFrom: DanConwayDev \u003cDanConwayDev@protonmail.com\u003e\nDate: Mon, 9 Dec 2024 09:34:08 +0000\nSubject: [PATCH 1/4] feat(NostrUrlDecoded) add nip05 support\n\nreplace `NostrUrlDecoded::from_str` with\n`NostrUrlDecoded::parse_and_resolve`\n\nstore nip05 pubkey mapping in git cache\n---\n src/bin/git_remote_nostr/main.rs | 12 ++++++------\n src/bin/ngit/sub_commands/init.rs | 10 ++++++----\n src/lib/git/nostr_url.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------\n src/lib/repo_ref.rs | 16 ++++++++++++----\n 4 files changed, 96 insertions(+), 23 deletions(-)\n\ndiff --git a/src/bin/git_remote_nostr/main.rs b/src/bin/git_remote_nostr/main.rs\nindex 8e12d68..84327d7 100644\n--- a/src/bin/git_remote_nostr/main.rs\n+++ b/src/bin/git_remote_nostr/main.rs\n@@ -9,7 +9,6 @@ use std::{\n collections::HashSet,\n env, io,\n path::{Path, PathBuf},\n- str::FromStr,\n };\n \n use anyhow::{bail, Context, Result};\n@@ -28,7 +27,7 @@ mod utils;\n \n #[tokio::main]\n async fn main() -\u003e Result\u003c()\u003e {\n- let Some((decoded_nostr_url, git_repo)) = process_args()? else {\n+ let Some((decoded_nostr_url, git_repo)) = process_args().await? else {\n return Ok(());\n };\n \n@@ -109,7 +108,7 @@ async fn main() -\u003e Result\u003c()\u003e {\n }\n }\n \n-fn process_args() -\u003e Result\u003cOption\u003c(NostrUrlDecoded, Repo)\u003e\u003e {\n+async fn process_args() -\u003e Result\u003cOption\u003c(NostrUrlDecoded, Repo)\u003e\u003e {\n let args = env::args();\n let args = args.skip(1).take(2).collect::\u003cVec\u003c_\u003e\u003e();\n \n@@ -135,13 +134,14 @@ fn process_args() -\u003e Result\u003cOption\u003c(NostrUrlDecoded, Repo)\u003e\u003e {\n return Ok(None);\n };\n \n- let decoded_nostr_url =\n- NostrUrlDecoded::from_str(nostr_remote_url).context(\"invalid nostr url\")?;\n-\n let git_repo = Repo::from_path(\u0026PathBuf::from(\n std::env::var(\"GIT_DIR\").context(\"git should set GIT_DIR when remote helper is called\")?,\n ))?;\n \n+ let decoded_nostr_url = NostrUrlDecoded::parse_and_resolve(nostr_remote_url, \u0026Some(\u0026git_repo))\n+ .await\n+ .context(\"invalid nostr url\")?;\n+\n Ok(Some((decoded_nostr_url, git_repo)))\n }\n \ndiff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs\nindex 6fc1ec4..c9c8873 100644\n--- a/src/bin/ngit/sub_commands/init.rs\n+++ b/src/bin/ngit/sub_commands/init.rs\n@@ -1,4 +1,4 @@\n-use std::{collections::HashMap, str::FromStr};\n+use std::collections::HashMap;\n \n use anyhow::{Context, Result};\n use console::Style;\n@@ -442,7 +442,7 @@ pub async fn launch(cli_args: \u0026Cli, args: \u0026SubCommandArgs) -\u003e Result\u003c()\u003e {\n .map(std::string::ToString::to_string)\n .collect::\u003cVec\u003cString\u003e\u003e();\n \n- prompt_to_set_nostr_url_as_origin(\u0026repo_ref, \u0026git_repo)?;\n+ prompt_to_set_nostr_url_as_origin(\u0026repo_ref, \u0026git_repo).await?;\n \n // TODO: if no state event exists and there is currently a remote called\n // \"origin\", automtically push rather than waiting for the next commit\n@@ -483,7 +483,7 @@ pub async fn launch(cli_args: \u0026Cli, args: \u0026SubCommandArgs) -\u003e Result\u003c()\u003e {\n Ok(())\n }\n \n-fn prompt_to_set_nostr_url_as_origin(repo_ref: \u0026RepoRef, git_repo: \u0026Repo) -\u003e Result\u003c()\u003e {\n+async fn prompt_to_set_nostr_url_as_origin(repo_ref: \u0026RepoRef, git_repo: \u0026Repo) -\u003e Result\u003c()\u003e {\n println!(\n \"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.\"\n );\n@@ -493,7 +493,9 @@ fn prompt_to_set_nostr_url_as_origin(repo_ref: \u0026RepoRef, git_repo: \u0026Repo) -\u003e Res\n \n if let Ok(origin_remote) = git_repo.git_repo.find_remote(\"origin\") {\n if let Some(origin_url) = origin_remote.url() {\n- if let Ok(nostr_url) = NostrUrlDecoded::from_str(origin_url) {\n+ if let Ok(nostr_url) =\n+ NostrUrlDecoded::parse_and_resolve(origin_url, \u0026Some(git_repo)).await\n+ {\n if nostr_url.coordinate.identifier == repo_ref.identifier {\n if nostr_url.coordinate.public_key == repo_ref.trusted_maintainer {\n return Ok(());\ndiff --git a/src/lib/git/nostr_url.rs b/src/lib/git/nostr_url.rs\nindex c26bb2e..ac57538 100644\n--- a/src/lib/git/nostr_url.rs\n+++ b/src/lib/git/nostr_url.rs\n@@ -2,9 +2,11 @@ use core::fmt;\n use std::str::FromStr;\n \n use anyhow::{anyhow, bail, Context, Error, Result};\n-use nostr::nips::nip01::Coordinate;\n+use nostr::nips::{nip01::Coordinate, nip05};\n use nostr_sdk::{PublicKey, RelayUrl, ToBech32, Url};\n \n+use super::{get_git_config_item, save_git_config_item, Repo};\n+\n #[derive(Debug, PartialEq, Default, Clone)]\n pub enum ServerProtocol {\n Ssh,\n@@ -59,6 +61,7 @@ pub struct NostrUrlDecoded {\n pub coordinate: Coordinate,\n pub protocol: Option\u003cServerProtocol\u003e,\n pub user: Option\u003cString\u003e,\n+ pub nip05: Option\u003cString\u003e,\n }\n \n impl fmt::Display for NostrUrlDecoded {\n@@ -89,10 +92,8 @@ impl fmt::Display for NostrUrlDecoded {\n \n static INCORRECT_NOSTR_URL_FORMAT_ERROR: \u0026str = \"incorrect nostr git url format. try nostr://naddr123 or nostr://npub123/my-repo or nostr://ssh/npub123/relay.damus.io/my-repo\";\n \n-impl std::str::FromStr for NostrUrlDecoded {\n- type Err = anyhow::Error;\n-\n- fn from_str(url: \u0026str) -\u003e Result\u003cSelf\u003e {\n+impl NostrUrlDecoded {\n+ pub async fn parse_and_resolve(url: \u0026str, git_repo: \u0026Option\u003c\u0026Repo\u003e) -\u003e Result\u003cSelf\u003e {\n let mut protocol = None;\n let mut user = None;\n let mut relays = vec![];\n@@ -154,6 +155,7 @@ impl std::str::FromStr for NostrUrlDecoded {\n }\n // extract naddr npub/\u003coptional-relays\u003e/identifer\n let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;\n+ let mut nip05 = None;\n // naddr used\n let coordinate = if let Ok(coordinate) = Coordinate::parse(part) {\n if coordinate.kind.eq(\u0026nostr_sdk::Kind::GitRepoAnnouncement) {\n@@ -161,8 +163,9 @@ impl std::str::FromStr for NostrUrlDecoded {\n } else {\n bail!(\"naddr doesnt point to a git repository announcement\");\n }\n- // npub/\u003coptional-relays\u003e/identifer used\n- } else if let Ok(public_key) = PublicKey::parse(part) {\n+ // \u003cnpub|nip05_address\u003e/\u003coptional-relays\u003e/identifer used\n+ } else {\n+ let npub_or_nip05 = part.to_owned();\n parts.remove(0);\n let identifier = parts\n .pop()\n@@ -179,14 +182,41 @@ impl std::str::FromStr for NostrUrlDecoded {\n RelayUrl::parse(\u0026decoded).context(\"could not parse relays in nostr git url\")?;\n relays.push(url);\n }\n+ let public_key = match PublicKey::parse(npub_or_nip05) {\n+ Ok(public_key) =\u003e public_key,\n+ Err(_) =\u003e {\n+ nip05 = Some(npub_or_nip05.to_string());\n+ if let Ok(public_key) =\n+ resolve_nip05_from_git_config_cache(npub_or_nip05, git_repo)\n+ {\n+ public_key\n+ } else {\n+ // TODO eprint loading message\n+ let res = nip05::profile(npub_or_nip05, None)\n+ .await\n+ .context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;\n+ // TODO clear loading message\n+ nip05 = Some(npub_or_nip05.to_string());\n+ let _ = save_nip05_to_git_config_cache(\n+ npub_or_nip05,\n+ \u0026res.public_key,\n+ git_repo,\n+ );\n+ if relays.is_empty() {\n+ for r in res.relays {\n+ relays.push(r);\n+ }\n+ }\n+ res.public_key\n+ }\n+ }\n+ };\n Coordinate {\n identifier,\n public_key,\n kind: nostr_sdk::Kind::GitRepoAnnouncement,\n relays,\n }\n- } else {\n- bail!(INCORRECT_NOSTR_URL_FORMAT_ERROR);\n };\n \n Ok(Self {\n@@ -194,10 +224,43 @@ impl std::str::FromStr for NostrUrlDecoded {\n coordinate,\n protocol,\n user,\n+ nip05,\n })\n }\n }\n \n+fn resolve_nip05_from_git_config_cache(nip05: \u0026str, git_repo: \u0026Option\u003c\u0026Repo\u003e) -\u003e Result\u003cPublicKey\u003e {\n+ let stored_value = get_git_config_item(\n+ git_repo,\n+ \u0026format!(\"nostr.nip05.{}\", urlencoding::encode(nip05)),\n+ )?\n+ .context(\"not in cache\")?;\n+ PublicKey::parse(stored_value)\n+ .context(\"stored nip05 resolution value did not parse as public key\")\n+}\n+\n+fn save_nip05_to_git_config_cache(\n+ nip05: \u0026str,\n+ public_key: \u0026PublicKey,\n+ git_repo: \u0026Option\u003c\u0026Repo\u003e,\n+) -\u003e Result\u003c()\u003e {\n+ if save_git_config_item(\n+ git_repo,\n+ \u0026format!(\"nostr.nip05.{}\", urlencoding::encode(nip05)),\n+ \u0026public_key.to_bech32()?,\n+ )\n+ .is_err()\n+ {\n+ save_git_config_item(\n+ \u0026None,\n+ \u0026format!(\"nostr.nip05.{}\", urlencoding::encode(nip05)),\n+ \u0026public_key.to_bech32()?,\n+ )\n+ } else {\n+ Ok(())\n+ }\n+}\n+\n #[derive(Debug, PartialEq, Default)]\n pub struct CloneUrl {\n original_string: String,\ndiff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs\nindex 1b25ccf..089befc 100644\n--- a/src/lib/repo_ref.rs\n+++ b/src/lib/repo_ref.rs\n@@ -41,6 +41,7 @@ impl TryFrom\u003c(nostr::Event, Option\u003cPublicKey\u003e)\u003e for RepoRef {\n type Error = anyhow::Error;\n \n fn try_from((event, trusted_maintainer): (nostr::Event, Option\u003cPublicKey\u003e)) -\u003e Result\u003cSelf\u003e {\n+ // TODO: turn trusted maintainer into NostrUrlDecoded\n if !event.kind.eq(\u0026Kind::GitRepoAnnouncement) {\n bail!(\"incorrect kind\");\n }\n@@ -239,6 +240,7 @@ impl RepoRef {\n coordinate: self.coordinate_with_hint(),\n protocol: None,\n user: None,\n+ nip05: None, // TODO: if nip05 for pubkey saved in local git config use it.\n }\n )\n }\n@@ -259,7 +261,7 @@ pub async fn get_repo_coordinates_when_remote_unknown(\n pub async fn try_and_get_repo_coordinates_when_remote_unknown(\n git_repo: \u0026Repo,\n ) -\u003e Result\u003cCoordinate\u003e {\n- let remote_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo)?;\n+ let remote_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo).await?;\n if remote_coordinates.is_empty() {\n if let Ok(c) = get_repo_coordinates_from_git_config(git_repo) {\n Ok(c)\n@@ -327,11 +329,15 @@ fn get_repo_coordinates_from_git_config(git_repo: \u0026Repo) -\u003e Result\u003cCoordinate\u003e {\n .context(\"git config item \\\"nostr.repo\\\" is not an naddr\")\n }\n \n-fn get_repo_coordinates_from_nostr_remotes(git_repo: \u0026Repo) -\u003e Result\u003cHashMap\u003cString, Coordinate\u003e\u003e {\n+async fn get_repo_coordinates_from_nostr_remotes(\n+ git_repo: \u0026Repo,\n+) -\u003e Result\u003cHashMap\u003cString, Coordinate\u003e\u003e {\n let mut repo_coordinates = HashMap::new();\n for remote_name in git_repo.git_repo.remotes()?.iter().flatten() {\n if let Some(remote_url) = git_repo.git_repo.find_remote(remote_name)?.url() {\n- if let Ok(nostr_url_decoded) = NostrUrlDecoded::from_str(remote_url) {\n+ if let Ok(nostr_url_decoded) =\n+ NostrUrlDecoded::parse_and_resolve(remote_url, \u0026Some(git_repo)).await\n+ {\n repo_coordinates.insert(remote_name.to_string(), nostr_url_decoded.coordinate);\n }\n }\n@@ -383,7 +389,9 @@ async fn get_repo_coordinate_from_user_prompt(\n .input(PromptInputParms::default().with_prompt(\"nostr repository\"))?;\n let coordinate = if let Ok(c) = Coordinate::parse(\u0026input) {\n c\n- } else if let Ok(nostr_url) = NostrUrlDecoded::from_str(\u0026input) {\n+ } else if let Ok(nostr_url) =\n+ NostrUrlDecoded::parse_and_resolve(\u0026input, \u0026Some(git_repo)).await\n+ {\n nostr_url.coordinate\n } else {\n eprintln!(\"not a valid naddr or git nostr remote URL starting nostr://\");\n--\nlibgit2 1.8.1\n\n",
"sig": "ec44713ca339775a3f9a1bc7e1e1cb652b6426bd30df6d336292df35e618e6b7db9bcb934a228e88720ca9186bdf1aa34f616f397ea5df2b5b16951e8da9978d"
}