diff options
Diffstat (limited to 'src/bin/git_remote_nostr')
| -rw-r--r-- | src/bin/git_remote_nostr/fetch.rs | 117 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/list.rs | 92 |
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 @@ | |||
| 1 | use core::str; | 1 | use core::str; |
| 2 | use std::{ | 2 | use 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 | ||
| 8 | use anyhow::{anyhow, bail, Result}; | 9 | use anyhow::{anyhow, bail, Context, Result}; |
| 9 | use auth_git2::GitAuthenticator; | 10 | use auth_git2::GitAuthenticator; |
| 10 | use git2::{Progress, Repository}; | 11 | use git2::{Progress, Repository}; |
| 11 | use ngit::{ | 12 | use ngit::{ |
| @@ -19,7 +20,7 @@ use ngit::{ | |||
| 19 | repo_ref::RepoRef, | 20 | repo_ref::RepoRef, |
| 20 | }; | 21 | }; |
| 21 | use nostr::nips::nip19; | 22 | use nostr::nips::nip19; |
| 22 | use nostr_sdk::ToBech32; | 23 | use nostr_sdk::{Event, ToBech32}; |
| 23 | 24 | ||
| 24 | use crate::utils::{ | 25 | use 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 | |||
| 88 | pub 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 | |||
| 131 | async 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, ¤t_user) | 144 | find_proposal_and_patches_by_branch_name(refstr, &open_proposals, ¤t_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 | ||
| 133 | fn fetch_from_git_server( | 159 | pub 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}; | |||
| 5 | use auth_git2::GitAuthenticator; | 5 | use auth_git2::GitAuthenticator; |
| 6 | use client::get_state_from_cache; | 6 | use client::get_state_from_cache; |
| 7 | use git::RepoActions; | 7 | use git::RepoActions; |
| 8 | use git_events::{event_to_cover_letter, get_commit_id_from_patch}; | ||
| 9 | use ngit::{ | 8 | use 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; | |||
| 20 | use repo_ref::RepoRef; | 19 | use repo_ref::RepoRef; |
| 21 | 20 | ||
| 22 | use crate::{ | 21 | use 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 | |||
| 113 | async 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 | ||
| 133 | pub fn list_from_remotes( | 177 | pub fn list_from_remotes( |