upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bin/git_remote_nostr
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/git_remote_nostr')
-rw-r--r--src/bin/git_remote_nostr/fetch.rs117
-rw-r--r--src/bin/git_remote_nostr/list.rs92
2 files changed, 143 insertions, 66 deletions
diff --git a/src/bin/git_remote_nostr/fetch.rs b/src/bin/git_remote_nostr/fetch.rs
index 2e16297..46e7ad3 100644
--- a/src/bin/git_remote_nostr/fetch.rs
+++ b/src/bin/git_remote_nostr/fetch.rs
@@ -1,11 +1,12 @@
1use core::str; 1use core::str;
2use std::{ 2use std::{
3 collections::HashMap,
3 io::Stdin, 4 io::Stdin,
4 sync::{Arc, Mutex}, 5 sync::{Arc, Mutex},
5 time::Instant, 6 time::Instant,
6}; 7};
7 8
8use anyhow::{anyhow, bail, Result}; 9use anyhow::{anyhow, bail, Context, Result};
9use auth_git2::GitAuthenticator; 10use auth_git2::GitAuthenticator;
10use git2::{Progress, Repository}; 11use git2::{Progress, Repository};
11use ngit::{ 12use ngit::{
@@ -19,7 +20,7 @@ use ngit::{
19 repo_ref::RepoRef, 20 repo_ref::RepoRef,
20}; 21};
21use nostr::nips::nip19; 22use nostr::nips::nip19;
22use nostr_sdk::ToBech32; 23use nostr_sdk::{Event, ToBech32};
23 24
24use crate::utils::{ 25use crate::utils::{
25 count_lines_per_msg_vec, fetch_or_list_error_is_not_authentication_failure, 26 count_lines_per_msg_vec, fetch_or_list_error_is_not_authentication_failure,
@@ -78,65 +79,97 @@ pub async fn run_fetch(
78 79
79 fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/pr/")); 80 fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/pr/"));
80 81
81 if !fetch_batch.is_empty() { 82 fetch_proposals(git_repo, &term, repo_ref, &fetch_batch).await?;
83 term.flush()?;
84 println!();
85 Ok(())
86}
87
88pub fn make_commits_for_proposal(
89 git_repo: &Repo,
90 repo_ref: &RepoRef,
91 patches_ancestor_last: &[Event],
92) -> Result<String> {
93 let patches_ancestor_first: Vec<&Event> = patches_ancestor_last.iter().rev().collect();
94 let mut tip_commit_id = if let Ok(parent_commit) = tag_value(
95 patches_ancestor_first
96 .first()
97 .context("proposal should have at least one patch")?,
98 "parent-commit",
99 ) {
100 parent_commit
101 } else {
102 // TODO choose most recent commit on master before patch timestamp so it doesnt
103 // constantly get rebased
104 let (_, hash) = git_repo.get_main_or_master_branch()?;
105 hash.to_string()
106 };
107
108 for patch in &patches_ancestor_first {
109 let commit_id = git_repo
110 .create_commit_from_patch(patch, Some(tip_commit_id.clone()))
111 .context(format!(
112 "cannot create commit for patch {}",
113 nip19::Nip19Event {
114 event_id: patch.id(),
115 author: Some(patch.author()),
116 kind: Some(patch.kind()),
117 relays: if let Some(relay) = repo_ref.relays.first() {
118 vec![relay.to_string()]
119 } else {
120 vec![]
121 },
122 }
123 .to_bech32()
124 .unwrap_or_default()
125 ))?;
126 tip_commit_id = commit_id.to_string();
127 }
128 Ok(tip_commit_id)
129}
130
131async fn fetch_proposals(
132 git_repo: &Repo,
133 term: &console::Term,
134 repo_ref: &RepoRef,
135 proposal_refs: &HashMap<String, String>,
136) -> Result<()> {
137 if !proposal_refs.is_empty() {
82 let open_proposals = get_open_proposals(git_repo, repo_ref).await?; 138 let open_proposals = get_open_proposals(git_repo, repo_ref).await?;
83 139
84 let current_user = get_curent_user(git_repo)?; 140 let current_user = get_curent_user(git_repo)?;
85 141
86 for (refstr, oid) in fetch_batch { 142 for refstr in proposal_refs.keys() {
87 if let Some((_, (_, patches))) = 143 if let Some((_, (_, patches))) =
88 find_proposal_and_patches_by_branch_name(&refstr, &open_proposals, &current_user) 144 find_proposal_and_patches_by_branch_name(refstr, &open_proposals, &current_user)
89 { 145 {
90 if !git_repo.does_commit_exist(&oid)? { 146 if let Err(error) = make_commits_for_proposal(git_repo, repo_ref, patches) {
91 let mut patches_ancestor_first = patches.clone(); 147 term.write_line(
92 patches_ancestor_first.reverse(); 148 format!("WARNING: cannot create branch for {refstr}, error: {error}",)
93 if git_repo.does_commit_exist(&tag_value( 149 .as_str(),
94 patches_ancestor_first.first().unwrap(), 150 )?;
95 "parent-commit", 151 break;
96 )?)? {
97 for patch in &patches_ancestor_first {
98 if let Err(error) = git_repo.create_commit_from_patch(patch) {
99 term.write_line(
100 format!(
101 "WARNING: cannot create branch for {refstr}, error: {error} for patch {}",
102 nip19::Nip19Event {
103 event_id: patch.id(),
104 author: Some(patch.author()),
105 kind: Some(patch.kind()),
106 relays: if let Some(relay) = repo_ref.relays.first() {
107 vec![relay.to_string()]
108 } else { vec![]},
109 }.to_bech32().unwrap_or_default()
110 )
111 .as_str(),
112 )?;
113 break;
114 }
115 }
116 } else {
117 term.write_line(
118 format!("WARNING: cannot find parent commit for {refstr}").as_str(),
119 )?;
120 }
121 } 152 }
122 } else {
123 term.write_line(format!("WARNING: cannot find proposal for {refstr}").as_str())?;
124 } 153 }
125 } 154 }
126 } 155 }
127
128 term.flush()?;
129 println!();
130 Ok(()) 156 Ok(())
131} 157}
132 158
133fn fetch_from_git_server( 159pub fn fetch_from_git_server(
134 git_repo: &Repo, 160 git_repo: &Repo,
135 oids: &[String], 161 oids: &[String],
136 git_server_url: &str, 162 git_server_url: &str,
137 decoded_nostr_url: &NostrUrlDecoded, 163 decoded_nostr_url: &NostrUrlDecoded,
138 term: &console::Term, 164 term: &console::Term,
139) -> Result<()> { 165) -> Result<()> {
166 let already_have_oids = oids
167 .iter()
168 .all(|oid| git_repo.does_commit_exist(oid).is_ok_and(|outcome| outcome));
169 if already_have_oids {
170 return Ok(());
171 }
172
140 let server_url = git_server_url.parse::<CloneUrl>()?; 173 let server_url = git_server_url.parse::<CloneUrl>()?;
141 174
142 let protocols_to_attempt = get_read_protocols_to_try(git_repo, &server_url, decoded_nostr_url); 175 let protocols_to_attempt = get_read_protocols_to_try(git_repo, &server_url, decoded_nostr_url);
diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs
index 959b8c8..378a124 100644
--- a/src/bin/git_remote_nostr/list.rs
+++ b/src/bin/git_remote_nostr/list.rs
@@ -5,14 +5,13 @@ use anyhow::{anyhow, Context, Result};
5use auth_git2::GitAuthenticator; 5use auth_git2::GitAuthenticator;
6use client::get_state_from_cache; 6use client::get_state_from_cache;
7use git::RepoActions; 7use git::RepoActions;
8use git_events::{event_to_cover_letter, get_commit_id_from_patch};
9use ngit::{ 8use ngit::{
10 client, 9 client,
11 git::{ 10 git::{
12 self, 11 self,
13 nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, 12 nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol},
14 }, 13 },
15 git_events, 14 git_events::event_to_cover_letter,
16 login::get_curent_user, 15 login::get_curent_user,
17 repo_ref, 16 repo_ref,
18}; 17};
@@ -20,6 +19,7 @@ use nostr_sdk::hashes::sha1::Hash as Sha1Hash;
20use repo_ref::RepoRef; 19use repo_ref::RepoRef;
21 20
22use crate::{ 21use crate::{
22 fetch::{fetch_from_git_server, make_commits_for_proposal},
23 git::Repo, 23 git::Repo,
24 utils::{ 24 utils::{
25 fetch_or_list_error_is_not_authentication_failure, get_open_proposals, 25 fetch_or_list_error_is_not_authentication_failure, get_open_proposals,
@@ -88,6 +88,61 @@ pub async fn run_list(
88 88
89 state.retain(|k, _| !k.starts_with("refs/heads/pr/")); 89 state.retain(|k, _| !k.starts_with("refs/heads/pr/"));
90 90
91 let proposals_state =
92 get_open_proposals_state(&term, git_repo, repo_ref, decoded_nostr_url, &remote_states)
93 .await?;
94
95 state.extend(proposals_state);
96
97 // TODO 'for push' should we check with the git servers to see if any of them
98 // allow push from the user?
99 for (name, value) in state {
100 if value.starts_with("ref: ") {
101 if !for_push {
102 println!("{} {name}", value.replace("ref: ", "@"));
103 }
104 } else {
105 println!("{value} {name}");
106 }
107 }
108
109 println!();
110 Ok(remote_states)
111}
112
113async fn get_open_proposals_state(
114 term: &console::Term,
115 git_repo: &Repo,
116 repo_ref: &RepoRef,
117 decoded_nostr_url: &NostrUrlDecoded,
118 remote_states: &HashMap<String, HashMap<String, String>>,
119) -> Result<HashMap<String, String>> {
120 // we cannot use commit_id in the latest patch in a proposal because:
121 // 1) the `commit` tag is optional
122 // 2) if the commit tag is wrong, it will cause errors which stop clone from
123 // working
124
125 // without trusting commit_id we must apply each patch which requires the oid of
126 // the parent so we much do a fetch
127 for (git_server_url, oids_from_git_servers) in remote_states {
128 if fetch_from_git_server(
129 git_repo,
130 &oids_from_git_servers
131 .values()
132 .filter(|v| !v.starts_with("ref: "))
133 .cloned()
134 .collect::<Vec<String>>(),
135 git_server_url,
136 decoded_nostr_url,
137 term,
138 )
139 .is_ok()
140 {
141 break;
142 }
143 }
144
145 let mut state = HashMap::new();
91 let open_proposals = get_open_proposals(git_repo, repo_ref).await?; 146 let open_proposals = get_open_proposals(git_repo, repo_ref).await?;
92 let current_user = get_curent_user(git_repo)?; 147 let current_user = get_curent_user(git_repo)?;
93 for (_, (proposal, patches)) in open_proposals { 148 for (_, (proposal, patches)) in open_proposals {
@@ -102,32 +157,21 @@ pub async fn run_list(
102 } else { 157 } else {
103 branch_name 158 branch_name
104 }; 159 };
105 if let Some(patch) = patches.first() { 160 match make_commits_for_proposal(git_repo, repo_ref, &patches) {
106 // TODO this isn't resilient because the commit id stated may not be correct 161 Ok(tip) => {
107 // we will need to check whether the commit id exists in the repo or apply the 162 state.insert(format!("refs/heads/{branch_name}"), tip);
108 // proposal and each patch to check
109 if let Ok(commit_id) = get_commit_id_from_patch(patch) {
110 state.insert(format!("refs/heads/{branch_name}"), commit_id);
111 } 163 }
112 } 164 Err(error) => {
113 } 165 let _ = term.write_line(
114 } 166 format!("WARNING: cannot fetch branch {branch_name} error: {error}")
115 } 167 .as_str(),
116 168 );
117 // TODO 'for push' should we check with the git servers to see if any of them 169 }
118 // allow push from the user? 170 };
119 for (name, value) in state {
120 if value.starts_with("ref: ") {
121 if !for_push {
122 println!("{} {name}", value.replace("ref: ", "@"));
123 } 171 }
124 } else {
125 println!("{value} {name}");
126 } 172 }
127 } 173 }
128 174 Ok(state)
129 println!();
130 Ok(remote_states)
131} 175}
132 176
133pub fn list_from_remotes( 177pub fn list_from_remotes(