diff --git a/src/bin/git_remote_nostr/main.rs b/src/bin/git_remote_nostr/main.rs index 5f9f712..1a21341 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(()); }; @@ -118,7 +117,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::>(); @@ -144,13 +143,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 7ed98f5..1695e4c 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; @@ -443,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 @@ -484,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." ); @@ -494,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(()); @@ -521,7 +523,7 @@ fn prompt_to_set_nostr_url_as_origin(repo_ref: &RepoRef, git_repo: &Repo) -> Res } } println!("contributors can clone your repository by installing ngit and using this clone url:"); - println!("{}", repo_ref.to_nostr_git_url()); + println!("{}", repo_ref.to_nostr_git_url(&Some(git_repo))); Ok(()) } @@ -534,7 +536,7 @@ fn ask_to_set_origin_remote(repo_ref: &RepoRef, git_repo: &Repo) -> Result<()> { )? { git_repo .git_repo - .remote_set_url("origin", &repo_ref.to_nostr_git_url())?; + .remote_set_url("origin", &repo_ref.to_nostr_git_url(&Some(git_repo)))?; } Ok(()) } @@ -547,7 +549,7 @@ fn ask_to_create_new_origin_remote(repo_ref: &RepoRef, git_repo: &Repo) -> Resul )? { git_repo .git_repo - .remote("origin", &repo_ref.to_nostr_git_url())?; + .remote("origin", &repo_ref.to_nostr_git_url(&Some(git_repo)))?; } Ok(()) } diff --git a/src/lib/git/nostr_url.rs b/src/lib/git/nostr_url.rs index c26bb2e..ca616a0 100644 --- a/src/lib/git/nostr_url.rs +++ b/src/lib/git/nostr_url.rs @@ -1,10 +1,12 @@ use core::fmt; -use std::str::FromStr; +use std::{collections::HashMap, 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![]; @@ -135,7 +136,9 @@ impl std::str::FromStr for NostrUrlDecoded { // extract optional protocol if protocol.is_none() { let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?; - let protocol_str = if let Some(at_index) = part.find('@') { + let protocol_str = if part.contains('.') { + part + } else if let Some(at_index) = part.find('@') { user = Some(part[..at_index].to_string()); &part[at_index + 1..] } else { @@ -146,7 +149,7 @@ impl std::str::FromStr for NostrUrlDecoded { "https" => Some(ServerProtocol::Https), "http" => Some(ServerProtocol::Http), "git" => Some(ServerProtocol::Git), - _ => protocol, + _ => None, }; if protocol.is_some() { parts.remove(0); @@ -154,6 +157,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 +165,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 +184,46 @@ 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 { + let term = console::Term::stderr(); + let domain = { + let s = npub_or_nip05.split('@').collect::>(); + if s.len() == 2 { s[1] } else { s[0] } + }; + term.write_line(&format!("fetching pubic key info from {domain}..."))?; + let res = nip05::profile(npub_or_nip05, None) + .await + .context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?; + term.clear_last_lines(1)?; + 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 +231,62 @@ impl std::str::FromStr for NostrUrlDecoded { coordinate, protocol, user, + nip05, }) } } +fn resolve_nip05_from_git_config_cache(nip05: &str, git_repo: &Option<&Repo>) -> Result { + if let Some(public_key) = load_nip_cache(git_repo)?.get(nip05) { + Ok(*public_key) + } else { + bail!("nip05 not stored in local git config cache") + } +} + +pub fn use_nip05_git_config_cache_to_find_nip05_from_public_key( + public_key: &PublicKey, + git_repo: &Option<&Repo>, +) -> Result> { + let h = load_nip_cache(git_repo)?; + Ok(h.iter() + .find_map(|(k, v)| if *v == *public_key { Some(k) } else { None }) + .cloned()) +} + +fn save_nip05_to_git_config_cache( + nip05: &str, + public_key: &PublicKey, + git_repo: &Option<&Repo>, +) -> Result<()> { + let mut h = load_nip_cache(git_repo)?; + h.insert(nip05.to_string(), *public_key); + + let s = h + .into_iter() + .map(|(nip05, public_key)| format!("{nip05}:{}", public_key.to_hex())) + .collect::>() + .join(","); + + save_git_config_item(git_repo, "nostr.nip05", s.as_str()) + .context("could not save nip05 cache in git config") +} + +fn load_nip_cache(git_repo: &Option<&Repo>) -> Result> { + let mut h = HashMap::new(); + let stored_value = get_git_config_item(git_repo, "nostr.nip05")? + .context("no nip05s in local git config cache so retun empty cache") + .unwrap_or_default(); + for pair in stored_value.split(',') { + if let Some((cached_nip05, pubkey)) = pair.split_once(':') { + if let Ok(public_key) = PublicKey::parse(pubkey) { + h.insert(cached_nip05.to_string(), public_key); + } + } + } + Ok(h) +} + #[derive(Debug, PartialEq, Default)] pub struct CloneUrl { original_string: String, @@ -887,6 +976,7 @@ mod tests { }, protocol: None, user: None, + nip05: None, } ), "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/nos.lol/ngit", @@ -912,6 +1002,7 @@ mod tests { }, protocol: None, user: None, + nip05: None, } ), "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit", @@ -937,6 +1028,7 @@ mod tests { }, protocol: Some(ServerProtocol::Ssh), user: None, + nip05: None, } ), "nostr://ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/nos.lol/ngit", @@ -962,6 +1054,7 @@ mod tests { }, protocol: Some(ServerProtocol::Ssh), user: Some("bla".to_string()), + nip05: None, } ), "nostr://bla@ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/nos.lol/ngit", @@ -971,8 +1064,6 @@ mod tests { } mod nostr_url_decoded_paramemters_from_str { - use std::str::FromStr; - use super::*; fn get_model_coordinate(relays: bool) -> Coordinate { @@ -991,11 +1082,11 @@ mod tests { } } - #[test] - fn from_naddr() -> Result<()> { + #[tokio::test] + async fn from_naddr() -> Result<()> { let url = "nostr://naddr1qqzxuemfwsqs6amnwvaz7tmwdaejumr0dspzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqvzqqqrhnym0k2qj".to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: Coordinate { @@ -1010,6 +1101,7 @@ mod tests { }, protocol: None, user: None, + nip05: None, }, ); Ok(()) @@ -1018,18 +1110,19 @@ mod tests { mod from_npub_slash_identifier { use super::*; - #[test] - fn without_relay() -> Result<()> { + #[tokio::test] + async fn without_relay() -> Result<()> { let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit" .to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(false), protocol: None, user: None, + nip05: None, }, ); Ok(()) @@ -1038,48 +1131,50 @@ mod tests { mod with_url_parameters { use super::*; - #[test] - fn with_relay_without_scheme_defaults_to_wss() -> Result<()> { + #[tokio::test] + async fn with_relay_without_scheme_defaults_to_wss() -> Result<()> { let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay=nos.lol".to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(true), protocol: None, user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_encoded_relay() -> Result<()> { + #[tokio::test] + async fn with_encoded_relay() -> Result<()> { let url = format!( "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}", urlencoding::encode("wss://nos.lol") ); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(true), protocol: None, user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_multiple_encoded_relays() -> Result<()> { + #[tokio::test] + async fn with_multiple_encoded_relays() -> Result<()> { let url = format!( "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}&relay1={}", urlencoding::encode("wss://nos.lol/"), urlencoding::encode("wss://relay.damus.io/"), ); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: Coordinate { @@ -1096,36 +1191,39 @@ mod tests { }, protocol: None, user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_server_protocol() -> Result<()> { + #[tokio::test] + async fn with_server_protocol() -> Result<()> { let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh".to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(false), protocol: Some(ServerProtocol::Ssh), user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_server_protocol_and_user() -> Result<()> { + #[tokio::test] + async fn with_server_protocol_and_user() -> Result<()> { let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh&user=fred".to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(false), protocol: Some(ServerProtocol::Ssh), user: Some("fred".to_string()), + nip05: None, }, ); Ok(()) @@ -1135,48 +1233,50 @@ mod tests { mod with_parameters_embedded_with_slashes { use super::*; - #[test] - fn with_relay_without_scheme_defaults_to_wss() -> Result<()> { + #[tokio::test] + async fn with_relay_without_scheme_defaults_to_wss() -> Result<()> { let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/nos.lol/ngit".to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(true), protocol: None, user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_encoded_relay() -> Result<()> { + #[tokio::test] + async fn with_encoded_relay() -> Result<()> { let url = format!( "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/ngit", urlencoding::encode("wss://nos.lol") ); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(true), protocol: None, user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_multiple_encoded_relays() -> Result<()> { + #[tokio::test] + async fn with_multiple_encoded_relays() -> Result<()> { let url = format!( "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/{}/ngit", urlencoding::encode("wss://nos.lol/"), urlencoding::encode("wss://relay.damus.io/"), ); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: Coordinate { @@ -1193,36 +1293,39 @@ mod tests { }, protocol: None, user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_server_protocol() -> Result<()> { + #[tokio::test] + async fn with_server_protocol() -> Result<()> { let url = "nostr://ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit".to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(false), protocol: Some(ServerProtocol::Ssh), user: None, + nip05: None, }, ); Ok(()) } - #[test] - fn with_server_protocol_and_user() -> Result<()> { + #[tokio::test] + async fn with_server_protocol_and_user() -> Result<()> { let url = "nostr://fred@ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit".to_string(); assert_eq!( - NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded::parse_and_resolve(&url, &None).await?, NostrUrlDecoded { original_string: url.clone(), coordinate: get_model_coordinate(false), protocol: Some(ServerProtocol::Ssh), user: Some("fred".to_string()), + nip05: None, }, ); Ok(()) diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs index da76182..5d6f4eb 100644 --- a/src/lib/repo_ref.rs +++ b/src/lib/repo_ref.rs @@ -19,7 +19,10 @@ use crate::{ Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms, PromptInputParms, }, client::{consolidate_fetch_reports, get_repo_ref_from_cache, sign_event, Connect}, - git::{nostr_url::NostrUrlDecoded, Repo, RepoActions}, + git::{ + nostr_url::{use_nip05_git_config_cache_to_find_nip05_from_public_key, NostrUrlDecoded}, + Repo, RepoActions, + }, login::user::get_user_details, }; @@ -41,6 +44,7 @@ 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"); } @@ -231,12 +235,18 @@ impl RepoRef { .collect::)>>() } - pub fn to_nostr_git_url(&self) -> String { + pub fn to_nostr_git_url(&self, git_repo: &Option<&Repo>) -> String { + let c = self.coordinate_with_hint(); format!( "{}", NostrUrlDecoded { original_string: String::new(), - coordinate: self.coordinate_with_hint(), + nip05: use_nip05_git_config_cache_to_find_nip05_from_public_key( + &c.public_key, + git_repo, + ) + .unwrap_or_default(), + coordinate: c, protocol: None, user: None, } @@ -259,7 +269,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 +337,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 +397,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://"); @@ -438,10 +454,10 @@ fn set_or_create_git_remote_with_nostr_url( repo_ref: &RepoRef, git_repo: &Repo, ) -> Result<()> { - let url = repo_ref.to_nostr_git_url(); + let url = repo_ref.to_nostr_git_url(&Some(git_repo)); if git_repo .git_repo - .remote_set_url(name, &repo_ref.to_nostr_git_url()) + .remote_set_url(name, &repo_ref.to_nostr_git_url(&Some(git_repo))) .is_err() { git_repo.git_repo.remote(name, &url)?;