upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin/git_remote_nostr/list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/git_remote_nostr/list.rs')
-rw-r--r--src/bin/git_remote_nostr/list.rs194
1 files changed, 194 insertions, 0 deletions
diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs
index e69de29..097f070 100644
--- a/src/bin/git_remote_nostr/list.rs
+++ b/src/bin/git_remote_nostr/list.rs
@@ -0,0 +1,194 @@
1use core::str;
2use std::collections::HashMap;
3
4use anyhow::{Context, Result};
5use auth_git2::GitAuthenticator;
6use client::get_state_from_cache;
7use git::RepoActions;
8use git_events::{event_to_cover_letter, get_commit_id_from_patch};
9use ngit::{client, git, git_events, login::get_curent_user, repo_ref};
10use nostr_sdk::hashes::sha1::Hash as Sha1Hash;
11use repo_ref::RepoRef;
12
13use crate::{
14 git::Repo,
15 utils::{
16 get_open_proposals, get_short_git_server_name, switch_clone_url_between_ssh_and_https,
17 },
18};
19
20pub async fn run_list(
21 git_repo: &Repo,
22 repo_ref: &RepoRef,
23 for_push: bool,
24) -> Result<HashMap<String, HashMap<String, String>>> {
25 let nostr_state =
26 if let Ok(nostr_state) = get_state_from_cache(git_repo.get_path()?, repo_ref).await {
27 Some(nostr_state)
28 } else {
29 None
30 };
31
32 let term = console::Term::stderr();
33
34 let remote_states = list_from_remotes(&term, git_repo, &repo_ref.git_server)?;
35
36 let mut state = if let Some(nostr_state) = nostr_state {
37 for (name, value) in &nostr_state.state {
38 for (url, remote_state) in &remote_states {
39 let remote_name = get_short_git_server_name(git_repo, url);
40 if let Some(remote_value) = remote_state.get(name) {
41 if value.ne(remote_value) {
42 term.write_line(
43 format!(
44 "WARNING: {remote_name} {name} is {} nostr ",
45 if let Ok((ahead, behind)) =
46 get_ahead_behind(git_repo, value, remote_value)
47 {
48 format!("{} ahead {} behind", ahead.len(), behind.len())
49 } else {
50 "out of sync with".to_string()
51 }
52 )
53 .as_str(),
54 )?;
55 }
56 } else {
57 term.write_line(
58 format!("WARNING: {remote_name} {name} is missing but tracked on nostr")
59 .as_str(),
60 )?;
61 }
62 }
63 }
64 nostr_state.state
65 } else {
66 repo_ref
67 .git_server
68 .iter()
69 .filter_map(|server| remote_states.get(server))
70 .cloned()
71 .collect::<Vec<HashMap<String, String>>>()
72 .first()
73 .context("failed to get refs from git server")?
74 .clone()
75 };
76
77 state.retain(|k, _| !k.starts_with("refs/heads/pr/"));
78
79 let open_proposals = get_open_proposals(git_repo, repo_ref).await?;
80 let current_user = get_curent_user(git_repo)?;
81 for (_, (proposal, patches)) in open_proposals {
82 if let Ok(cl) = event_to_cover_letter(&proposal) {
83 if let Ok(mut branch_name) = cl.get_branch_name() {
84 branch_name = if let Some(public_key) = current_user {
85 if proposal.author().eq(&public_key) {
86 cl.branch_name.to_string()
87 } else {
88 branch_name
89 }
90 } else {
91 branch_name
92 };
93 if let Some(patch) = patches.first() {
94 // TODO this isn't resilient because the commit id stated may not be correct
95 // we will need to check whether the commit id exists in the repo or apply the
96 // proposal and each patch to check
97 if let Ok(commit_id) = get_commit_id_from_patch(patch) {
98 state.insert(format!("refs/heads/{branch_name}"), commit_id);
99 }
100 }
101 }
102 }
103 }
104
105 // TODO 'for push' should we check with the git servers to see if any of them
106 // allow push from the user?
107 for (name, value) in state {
108 if value.starts_with("ref: ") {
109 if !for_push {
110 println!("{} {name}", value.replace("ref: ", "@"));
111 }
112 } else {
113 println!("{value} {name}");
114 }
115 }
116
117 println!();
118 Ok(remote_states)
119}
120
121pub fn list_from_remotes(
122 term: &console::Term,
123 git_repo: &Repo,
124 git_servers: &Vec<String>,
125) -> Result<HashMap<String, HashMap<String, String>>> {
126 let mut remote_states = HashMap::new();
127 for url in git_servers {
128 let short_name = get_short_git_server_name(git_repo, url);
129 term.write_line(format!("fetching refs list: {short_name}...").as_str())?;
130 match list_from_remote(git_repo, url) {
131 Ok(remote_state) => {
132 remote_states.insert(url.clone(), remote_state);
133 }
134 Err(error1) => {
135 if let Ok(alternative_url) = switch_clone_url_between_ssh_and_https(url) {
136 match list_from_remote(git_repo, &alternative_url) {
137 Ok(remote_state) => {
138 remote_states.insert(url.clone(), remote_state);
139 }
140 Err(error2) => {
141 term.write_line(
142 format!("WARNING: {short_name} failed to list refs error: {error1}\r\nand alternative protocol {alternative_url}: {error2}").as_str(),
143 )?;
144 }
145 }
146 } else {
147 term.write_line(
148 format!("WARNING: {short_name} failed to list refs error: {error1}",)
149 .as_str(),
150 )?;
151 }
152 }
153 }
154 term.clear_last_lines(1)?;
155 }
156 Ok(remote_states)
157}
158
159fn list_from_remote(
160 git_repo: &Repo,
161 git_server_remote_url: &str,
162) -> Result<HashMap<String, String>> {
163 let git_config = git_repo.git_repo.config()?;
164
165 let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_remote_url)?;
166 // authentication may be required
167 let auth = GitAuthenticator::default();
168 let mut remote_callbacks = git2::RemoteCallbacks::new();
169 remote_callbacks.credentials(auth.credentials(&git_config));
170 git_server_remote.connect_auth(git2::Direction::Fetch, Some(remote_callbacks), None)?;
171 let mut state = HashMap::new();
172 for head in git_server_remote.list()? {
173 if let Some(symbolic_reference) = head.symref_target() {
174 state.insert(
175 head.name().to_string(),
176 format!("ref: {symbolic_reference}"),
177 );
178 } else {
179 state.insert(head.name().to_string(), head.oid().to_string());
180 }
181 }
182 git_server_remote.disconnect()?;
183 Ok(state)
184}
185
186fn get_ahead_behind(
187 git_repo: &Repo,
188 base_ref_or_oid: &str,
189 latest_ref_or_oid: &str,
190) -> Result<(Vec<Sha1Hash>, Vec<Sha1Hash>)> {
191 let base = git_repo.get_commit_or_tip_of_reference(base_ref_or_oid)?;
192 let latest = git_repo.get_commit_or_tip_of_reference(latest_ref_or_oid)?;
193 git_repo.get_commits_ahead_behind(&base, &latest)
194}