Avatar
gnostr
a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd
gnostr://1898/931076

1923/913588/424196

1923/913588/424185

1923/913585/428179

1923/913585/428134

1923/913585/428102

1923/913585/428100

1923/913583/430418

1923/913583/430410

1923/913583/430396

1923/913583/430388

1923/913582/431016

1923/913582/430991

1923/913582/430988

1923/913582/430986

1923/913575/442182

1923/913575/442180

1923/913575/442145

1923/913574/444018

1923/913573/444017

1923/913573/444010

1923/913538/491598

1923/913520/516327

1923/913520/516290

Replying to Avatar DanConwayDev

From 28f15e8be3e4e6205e2c6ce09ab71ea71f358d6e Mon Sep 17 00:00:00 2001

From: DanConwayDev

Date: Wed, 27 Aug 2025 16:38:12 +0100

Subject: [PATCH] remote: fix out of sync git servers during list

so maintainers don't need to to run `ngit sync` to fix a repo

created in response to issue:

nostr:nevent1qvzqqqqx25pzpslz866785q0rze0favgmrxmc4yxfzl8vx7ajzqjrpklgcpa0j4fqqs2qt4vxhr37c9u4q9xy4u7ln2w96anv7h4965kef6d0kdygp82zscy5sc79

---

src/bin/git_remote_nostr/list.rs | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------

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

src/lib/git/mod.rs | 10 ++++++++++

3 files changed, 131 insertions(+), 31 deletions(-)

diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs

index be83991..9735f72 100644

--- a/src/bin/git_remote_nostr/list.rs

+++ b/src/bin/git_remote_nostr/list.rs

@@ -10,6 +10,7 @@ use ngit::{

git_events::{KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_to_cover_letter, tag_value},

list::{get_ahead_behind, list_from_remotes},

login::get_curent_user,

+ push::push_to_remote,

repo_ref::{self},

utils::{get_all_proposals, get_open_or_draft_proposals, get_short_git_server_name},

};

@@ -20,6 +21,7 @@ use crate::{fetch::make_commits_for_proposal, git::Repo};

pub async fn run_list(

git_repo: &Repo,

repo_ref: &RepoRef,

+ user_is_known_and_maintainer: bool,

for_push: bool,

) -> Result, bool)>> {

let nostr_state = (get_state_from_cache(Some(git_repo.get_path()?), repo_ref).await).ok();

@@ -35,33 +37,14 @@ pub async fn run_list(

);

let mut state = if let Some(nostr_state) = nostr_state {

- for (name, value) in &nostr_state.state {

- for (url, (remote_state, _is_grasp_server)) in &remote_states {

- let remote_name = get_short_git_server_name(git_repo, url);

- if let Some(remote_value) = remote_state.get(name) {

- if value.ne(remote_value) {

- term.write_line(

- format!(

- "WARNING: {remote_name} {name} is {} nostr ",

- if let Ok((ahead, behind)) =

- get_ahead_behind(git_repo, value, remote_value)

- {

- format!("{} ahead {} behind", ahead.len(), behind.len())

- } else {

- "out of sync with".to_string()

- }

- )

- .as_str(),

- )?;

- }

- } else {

- term.write_line(

- format!("WARNING: {remote_name} {name} is missing but tracked on nostr")

- .as_str(),

- )?;

- }

- }

- }

+ sync_git_servers_with_nostr_state(

+ git_repo,

+ repo_ref,

+ &term,

+ &nostr_state.state,

+ &remote_states,

+ user_is_known_and_maintainer,

+ )?;

nostr_state.state

} else {

let (state, _is_grasp_server) = repo_ref

@@ -104,6 +87,101 @@ pub async fn run_list(

Ok(remote_states)

}

+fn sync_git_servers_with_nostr_state(

+ git_repo: &Repo,

+ repo_ref: &RepoRef,

+ term: &console::Term,

+ nostr_state: &HashMap,

+ remote_states: &HashMap, bool)>,

+ user_is_known_and_maintainer: bool,

+) -> Result<()> {

+ let mut to_immediately_sync: HashMap, bool)> = HashMap::new();

+ for (name, value) in nostr_state {

+ for (url, (remote_state, is_grasp_server)) in remote_states {

+ let remote_name = get_short_git_server_name(git_repo, url);

+ if let Some(remote_value) = remote_state.get(name) {

+ if value.ne(remote_value) {

+ if let Ok((ahead, behind)) = get_ahead_behind(git_repo, value, remote_value) {

+ // TODO maybe we should try and fix it and only warn if we cant?

+ term.write_line(

+ format!(

+ "WARNING: {remote_name} {name} is {} ahead {} behind nostr",

+ ahead.len(),

+ behind.len()

+ )

+ .as_str(),

+ )?;

+ if *is_grasp_server {

+ if git_repo.does_oid_exist(value).is_ok_and(|v| v) {

+ to_immediately_sync

+ .entry(url.to_string())

+ .or_insert((Vec::new(), *is_grasp_server))

+ .0

+ .push(format!(

+ "{}{value}:{name}",

+ if *is_grasp_server { "+" } else { "" }

+ ));

+ }

+ } else if behind.is_empty() {

+ if user_is_known_and_maintainer {

+ to_immediately_sync

+ .entry(url.to_string())

+ .or_insert((Vec::new(), *is_grasp_server))

+ .0

+ .push(format!("{value}:{name}"));

+ } else {

+ // ff push to non-grasp server by someone with

+ // keys required

+ }

+ } else if user_is_known_and_maintainer {

+ // TODO warn maintainer about conflicts and

+ // suggesting running `ngit sync --force`

+ }

+ } else {

+ // here we probably dont have the git data to push

+ term.write_line(

+ format!("WARNING: {remote_name} {name} is out of sync with nostr ")

+ .as_str(),

+ )?;

+ }

+ }

+ } else {

+ term.write_line(

+ format!("WARNING: {remote_name} {name} is missing but tracked on nostr")

+ .as_str(),

+ )?;

+ to_immediately_sync

+ .entry(url.to_string())

+ .or_insert((Vec::new(), *is_grasp_server))

+ .0

+ .push(format!("{value}:{name}"));

+ }

+ }

+ }

+ if !to_immediately_sync.is_empty() {

+ term.write_line("attempting to align git servers with nostr state")?;

+

+ for (url, (remote_refspecs, is_grasp_server)) in to_immediately_sync {

+ if let Err(error) = push_to_remote(

+ git_repo,

+ &url,

+ &repo_ref.nostr_git_url.clone().context("run_list should only be called in git-remote-nostr where nostr_git_url is always set")?,

+ &remote_refspecs,

+ term,

+ is_grasp_server,

+ ) {

+ let remote_name = get_short_git_server_name(git_repo, &url);

+

+ term.write_line(

+ format!(" - failed to align {remote_name} with nostr state: {error}")

+ .as_str(),

+ )?;

+ }

+ }

+ }

+ Ok(())

+}

+

/// fetches branches and tags from git servers so patch parent commits can be

/// used to build patches with correct commit ids

async fn get_open_and_draft_proposals_state(

diff --git a/src/bin/git_remote_nostr/main.rs b/src/bin/git_remote_nostr/main.rs

index aac626b..c544d76 100644

--- a/src/bin/git_remote_nostr/main.rs

+++ b/src/bin/git_remote_nostr/main.rs

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

let mut client = Client::new(Params::with_git_config_relay_defaults(&Some(&git_repo)));

- if let Ok((signer, _, _)) = load_existing_login(

+ let user_ref = if let Ok((signer, user_ref, _)) = load_existing_login(

&Some(&git_repo),

&None,

&None,

@@ -52,7 +52,10 @@ async fn main() -> Result<()> {

{

// signer for to respond to relay auth request

client.set_signer(signer).await;

- }

+ Some(user_ref)

+ } else {

+ None

+ };

fetching_with_report_for_helper(git_repo_path, &client, &decoded_nostr_url.coordinate).await?;

@@ -61,6 +64,9 @@ async fn main() -> Result<()> {

repo_ref.set_nostr_git_url(decoded_nostr_url.clone());

+ let user_is_known_and_maintainer =

+ user_ref.is_some_and(|u| repo_ref.maintainers.contains(&u.public_key));

+

let _ = set_git_timeout();

let stdin = io::stdin();

@@ -98,10 +104,16 @@ async fn main() -> Result<()> {

.await?;

}

["list"] => {

- list_outputs = Some(list::run_list(&git_repo, &repo_ref, false).await?);

+ list_outputs = Some(

+ list::run_list(&git_repo, &repo_ref, user_is_known_and_maintainer, false)

+ .await?,

+ );

}

["list", "for-push"] => {

- list_outputs = Some(list::run_list(&git_repo, &repo_ref, true).await?);

+ list_outputs = Some(

+ list::run_list(&git_repo, &repo_ref, user_is_known_and_maintainer, true)

+ .await?,

+ );

}

[] => {

return Ok(());

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

index 3d5297f..f161605 100644

--- a/src/lib/git/mod.rs

+++ b/src/lib/git/mod.rs

@@ -50,6 +50,7 @@ pub trait RepoActions {

fn get_commit_or_tip_of_reference(&self, reference: &str) -> Result;

fn get_root_commit(&self) -> Result;

fn does_commit_exist(&self, commit: &str) -> Result;

+ fn does_oid_exist(&self, commit: &str) -> Result;

fn get_head_commit(&self) -> Result;

fn get_commit_parent(&self, commit: &Sha1Hash) -> Result;

fn get_commit_message(&self, commit: &Sha1Hash) -> Result;

@@ -275,6 +276,15 @@ impl RepoActions for Repo {

}

}

+ fn does_oid_exist(&self, oid: &str) -> Result {

+ let oid_obj = Oid::from_str(oid).context("not an oid hash")?;

+ if self.git_repo.find_object(oid_obj, None).is_ok() {

+ Ok(true)

+ } else {

+ Ok(false)

+ }

+ }

+

fn get_head_commit(&self) -> Result {

let head = self

.git_repo

--

libgit2 1.9.1

Test reply

Replying to Avatar daniele

From d9b0206234852ab66b42d7a0563c55235b1f763f Mon Sep 17 00:00:00 2001

From: dtonon

Date: Mon, 1 Sep 2025 15:48:27 +0200

Subject: [PATCH 3/3] Add oracolo.me

---

apps.toml | 22 ++++++++++++++++++++++

1 file changed, 22 insertions(+)

diff --git a/apps.toml b/apps.toml

index 132c4d1..4ed871f 100644

--- a/apps.toml

+++ b/apps.toml

@@ -1521,3 +1521,25 @@ platforms = ["web"]

thumb = ""

url = "https://nstart.me"

source = "https://github.com/dtonon/nstart"

+

+[oracolo]

+name = "Oracolo.me"

+categories = ["blogging", "microblogging", "tools"]

+description = "A minimalist Nostr blog that consists of a single html file"

+gallery = [

+ "",

+ "",

+ "",

+ "",

+]

+features = [

+ "Use an online wizard to easily create your own blog, powered by Nostr",

+ "Freely design the blog's structure using content blocks",

+ "Export the full blog with a single HTML file for self hosting, or",

+ "Enjoy the free Oracolo service and go online just pointing your domain",

+]

+npub = "npub10000003zmk89narqpczy4ff6rnuht2wu05na7kpnh3mak7z2tqzsv8vwqk"

+platforms = ["web"]

+thumb = ""

+url = "https://oracolo.me"

+source = "https://github.com/dtonon/oracolo"

--

libgit2 1.9.1

Test reply

1923/913484/567127

1923/913454/608046