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:
---
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
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
+ user_is_known_and_maintainer: bool,
+) -> Result<()> {
+ let mut to_immediately_sync: HashMap
+ 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