From b4d81f54ad6e5cbf212593527727e7f7cb036aef Mon Sep 17 00:00:00 2001

From: DanConwayDev

Date: Mon, 9 Dec 2024 09:34:08 +0000

Subject: [PATCH] add nostr_url nip05 whilst retaining structure

Following the discussion:

nostr:nevent1qvzqqqqqqypzpn7hma38nx3zuwz2f26a4rzqymy8tvge6r68cfckkgxd4jwvrudxqyt8wumn8ghj7ur4wfcxcetjv4kxz7fwvdhk6tcppemhxue69uhkummn9ekx7mp0qythwumn8ghj7un9d3shjtnwdaehgu3wvfskuep0qyt8wumn8ghj7ur4wfcxcetjv4kxz7fwvdhk6tcppemhxue69uhkummn9ekx7mp0qythwumn8ghj7un9d3shjtnwdaehgu3wvfskuep0qqsf26xu3q0x4mzfz2nu76pcj42qthh30hwwrclzdhj3n60qvwe54dcrghvk2

I've pulled this together to demonstrate how this feature could be

added to `NostrUrlDecoded` without dramatically changing its

structure or adding a similar struct which omits the `PublicKey`.

it replaces `NostrUrlDecoded::from_str` with

`NostrUrlDecoded::parse_and_resolve`.

In this WIP I have began to add nip05 to repo_ref so that

`RepoRef::to_nostr_git_url` can render the nip05 when originally

used. Unfortunately, there is more work to as the type of

`trusted_maintainer` needs to change to `NostrUrlDecoded`.

There may still be instances where the variable name

`repo_coordinate` is used to create repo_refs when it is exclusively

refering to trusted maintainers and not other repo maintainers.

these should use the type `NostrUrlDecoded` too, to ensure

`RepoRef::to_nostr_git_url` always includes nip05 when originally

taken from the nostr_url.

---

src/bin/git_remote_nostr/main.rs | 12 ++++++------

src/bin/ngit/sub_commands/init.rs | 11 +++++++----

src/lib/git/nostr_url.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------

src/lib/repo_ref.rs | 19 +++++++++++++++----

4 files changed, 100 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::{

collections::HashSet,

env, io,

path::{Path, PathBuf},

- str::FromStr,

};

use anyhow::{bail, Context, Result};

@@ -28,7 +27,7 @@ mod utils;

#[tokio::main]

async fn main() -> Result<()> {

- let Some((decoded_nostr_url, git_repo)) = process_args()? else {

+ let Some((decoded_nostr_url, git_repo)) = process_args().await? else {

return Ok(());

};

@@ -109,7 +108,7 @@ async fn main() -> Result<()> {

}

}

-fn process_args() -> Result> {

+async fn process_args() -> Result> {

let args = env::args();

let args = args.skip(1).take(2).collect::>();

@@ -135,13 +134,14 @@ fn process_args() -> Result> {

return Ok(None);

};

- let decoded_nostr_url =

- NostrUrlDecoded::from_str(nostr_remote_url).context("invalid nostr url")?;

-

let git_repo = Repo::from_path(&PathBuf::from(

std::env::var("GIT_DIR").context("git should set GIT_DIR when remote helper is called")?,

))?;

+ let decoded_nostr_url = NostrUrlDecoded::parse_and_resolve(nostr_remote_url, &Some(&git_repo))

+ .await

+ .context("invalid nostr url")?;

+

Ok(Some((decoded_nostr_url, git_repo)))

}

diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs

index 6fc1ec4..dbad9b6 100644

--- a/src/bin/ngit/sub_commands/init.rs

+++ b/src/bin/ngit/sub_commands/init.rs

@@ -1,4 +1,4 @@

-use std::{collections::HashMap, str::FromStr};

+use std::collections::HashMap;

use anyhow::{Context, Result};

use console::Style;

@@ -408,6 +408,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {

trusted_maintainer: user_ref.public_key,

maintainers: maintainers.clone(),

events: HashMap::new(),

+ nip05: None,

};

let repo_event = repo_ref.to_event(&signer).await?;

@@ -442,7 +443,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {

.map(std::string::ToString::to_string)

.collect::>();

- prompt_to_set_nostr_url_as_origin(&repo_ref, &git_repo)?;

+ prompt_to_set_nostr_url_as_origin(&repo_ref, &git_repo).await?;

// TODO: if no state event exists and there is currently a remote called

// "origin", automtically push rather than waiting for the next commit

@@ -483,7 +484,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {

Ok(())

}

-fn prompt_to_set_nostr_url_as_origin(repo_ref: &RepoRef, git_repo: &Repo) -> Result<()> {

+async fn prompt_to_set_nostr_url_as_origin(repo_ref: &RepoRef, git_repo: &Repo) -> Result<()> {

println!(

"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."

);

@@ -493,7 +494,9 @@ fn prompt_to_set_nostr_url_as_origin(repo_ref: &RepoRef, git_repo: &Repo) -> Res

if let Ok(origin_remote) = git_repo.git_repo.find_remote("origin") {

if let Some(origin_url) = origin_remote.url() {

- if let Ok(nostr_url) = NostrUrlDecoded::from_str(origin_url) {

+ if let Ok(nostr_url) =

+ NostrUrlDecoded::parse_and_resolve(origin_url, &Some(git_repo)).await

+ {

if nostr_url.coordinate.identifier == repo_ref.identifier {

if nostr_url.coordinate.public_key == repo_ref.trusted_maintainer {

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;

use std::str::FromStr;

use anyhow::{anyhow, bail, Context, Error, Result};

-use nostr::nips::nip01::Coordinate;

+use nostr::nips::{nip01::Coordinate, nip05};

use nostr_sdk::{PublicKey, RelayUrl, ToBech32, Url};

+use super::{get_git_config_item, save_git_config_item, Repo};

+

#[derive(Debug, PartialEq, Default, Clone)]

pub enum ServerProtocol {

Ssh,

@@ -59,6 +61,7 @@ pub struct NostrUrlDecoded {

pub coordinate: Coordinate,

pub protocol: Option,

pub user: Option,

+ pub nip05: Option,

}

impl fmt::Display for NostrUrlDecoded {

@@ -89,10 +92,8 @@ impl fmt::Display for NostrUrlDecoded {

static 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";

-impl std::str::FromStr for NostrUrlDecoded {

- type Err = anyhow::Error;

-

- fn from_str(url: &str) -> Result {

+impl NostrUrlDecoded {

+ pub async fn parse_and_resolve(url: &str, git_repo: &Option<&Repo>) -> Result {

let mut protocol = None;

let mut user = None;

let mut relays = vec![];

@@ -154,6 +155,7 @@ impl std::str::FromStr for NostrUrlDecoded {

}

// extract naddr npub//identifer

let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;

+ let mut nip05 = None;

// naddr used

let coordinate = if let Ok(coordinate) = Coordinate::parse(part) {

if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) {

@@ -161,8 +163,9 @@ impl std::str::FromStr for NostrUrlDecoded {

} else {

bail!("naddr doesnt point to a git repository announcement");

}

- // npub//identifer used

- } else if let Ok(public_key) = PublicKey::parse(part) {

+ // //identifer used

+ } else {

+ let npub_or_nip05 = part.to_owned();

parts.remove(0);

let identifier = parts

.pop()

@@ -179,14 +182,41 @@ impl std::str::FromStr for NostrUrlDecoded {

RelayUrl::parse(&decoded).context("could not parse relays in nostr git url")?;

relays.push(url);

}

+ let public_key = match PublicKey::parse(npub_or_nip05) {

+ Ok(public_key) => public_key,

+ Err(_) => {

+ nip05 = Some(npub_or_nip05.to_string());

+ if let Ok(public_key) =

+ resolve_nip05_from_git_config_cache(npub_or_nip05, git_repo)

+ {

+ public_key

+ } else {

+ // TODO eprint loading message

+ let res = nip05::profile(npub_or_nip05, None)

+ .await

+ .context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?;

+ // TODO clear loading message

+ nip05 = Some(npub_or_nip05.to_string());

+ let _ = save_nip05_to_git_config_cache(

+ npub_or_nip05,

+ &res.public_key,

+ git_repo,

+ );

+ if relays.is_empty() {

+ for r in res.relays {

+ relays.push(r);

+ }

+ }

+ res.public_key

+ }

+ }

+ };

Coordinate {

identifier,

public_key,

kind: nostr_sdk::Kind::GitRepoAnnouncement,

relays,

}

- } else {

- bail!(INCORRECT_NOSTR_URL_FORMAT_ERROR);

};

Ok(Self {

@@ -194,10 +224,43 @@ impl std::str::FromStr for NostrUrlDecoded {

coordinate,

protocol,

user,

+ nip05,

})

}

}

+fn resolve_nip05_from_git_config_cache(nip05: &str, git_repo: &Option<&Repo>) -> Result {

+ let stored_value = get_git_config_item(

+ git_repo,

+ &format!("nostr.nip05.{}", urlencoding::encode(nip05)),

+ )?

+ .context("not in cache")?;

+ PublicKey::parse(stored_value)

+ .context("stored nip05 resolution value did not parse as public key")

+}

+

+fn save_nip05_to_git_config_cache(

+ nip05: &str,

+ public_key: &PublicKey,

+ git_repo: &Option<&Repo>,

+) -> Result<()> {

+ if save_git_config_item(

+ git_repo,

+ &format!("nostr.nip05.{}", urlencoding::encode(nip05)),

+ &public_key.to_bech32()?,

+ )

+ .is_err()

+ {

+ save_git_config_item(

+ &None,

+ &format!("nostr.nip05.{}", urlencoding::encode(nip05)),

+ &public_key.to_bech32()?,

+ )

+ } else {

+ Ok(())

+ }

+}

+

#[derive(Debug, PartialEq, Default)]

pub struct CloneUrl {

original_string: String,

diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs

index 1b25ccf..bf23d30 100644

--- a/src/lib/repo_ref.rs

+++ b/src/lib/repo_ref.rs

@@ -35,12 +35,14 @@ pub struct RepoRef {

pub maintainers: Vec,

pub trusted_maintainer: PublicKey,

pub events: HashMap,

+ pub nip05: Option,

}

impl TryFrom<(nostr::Event, Option)> for RepoRef {

type Error = anyhow::Error;

fn try_from((event, trusted_maintainer): (nostr::Event, Option)) -> Result {

+ // TODO: turn trusted maintainer into NostrUrlDecoded

if !event.kind.eq(&Kind::GitRepoAnnouncement) {

bail!("incorrect kind");

}

@@ -56,6 +58,7 @@ impl TryFrom<(nostr::Event, Option)> for RepoRef {

maintainers: Vec::new(),

trusted_maintainer: trusted_maintainer.unwrap_or(event.pubkey),

events: HashMap::new(),

+ nip05: None,

};

for tag in event.tags.iter() {

@@ -239,6 +242,7 @@ impl RepoRef {

coordinate: self.coordinate_with_hint(),

protocol: None,

user: None,

+ nip05: self.nip05.clone(),

}

)

}

@@ -259,7 +263,7 @@ pub async fn get_repo_coordinates_when_remote_unknown(

pub async fn try_and_get_repo_coordinates_when_remote_unknown(

git_repo: &Repo,

) -> Result {

- let remote_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo)?;

+ let remote_coordinates = get_repo_coordinates_from_nostr_remotes(git_repo).await?;

if remote_coordinates.is_empty() {

if let Ok(c) = get_repo_coordinates_from_git_config(git_repo) {

Ok(c)

@@ -327,11 +331,15 @@ fn get_repo_coordinates_from_git_config(git_repo: &Repo) -> Result {

.context("git config item \"nostr.repo\" is not an naddr")

}

-fn get_repo_coordinates_from_nostr_remotes(git_repo: &Repo) -> Result> {

+async fn get_repo_coordinates_from_nostr_remotes(

+ git_repo: &Repo,

+) -> Result> {

let mut repo_coordinates = HashMap::new();

for remote_name in git_repo.git_repo.remotes()?.iter().flatten() {

if let Some(remote_url) = git_repo.git_repo.find_remote(remote_name)?.url() {

- if let Ok(nostr_url_decoded) = NostrUrlDecoded::from_str(remote_url) {

+ if let Ok(nostr_url_decoded) =

+ NostrUrlDecoded::parse_and_resolve(remote_url, &Some(git_repo)).await

+ {

repo_coordinates.insert(remote_name.to_string(), nostr_url_decoded.coordinate);

}

}

@@ -383,7 +391,9 @@ async fn get_repo_coordinate_from_user_prompt(

.input(PromptInputParms::default().with_prompt("nostr repository"))?;

let coordinate = if let Ok(c) = Coordinate::parse(&input) {

c

- } else if let Ok(nostr_url) = NostrUrlDecoded::from_str(&input) {

+ } else if let Ok(nostr_url) =

+ NostrUrlDecoded::parse_and_resolve(&input, &Some(git_repo)).await

+ {

nostr_url.coordinate

} else {

eprintln!("not a valid naddr or git nostr remote URL starting nostr://");

@@ -540,6 +550,7 @@ mod tests {

trusted_maintainer: TEST_KEY_1_KEYS.public_key(),

maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()],

events: HashMap::new(),

+ nip05: None,

}

.to_event(&TEST_KEY_1_SIGNER)

.await

--

libgit2 1.8.1

Reply to this note

Please Login to reply.

Discussion

Great, just what I wanted :)

I was too coward to remove from_str trait.

And cloning nip05 repo already works!