upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin/ngit/sub_commands/sync.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/ngit/sub_commands/sync.rs')
-rw-r--r--src/bin/ngit/sub_commands/sync.rs115
1 files changed, 111 insertions, 4 deletions
diff --git a/src/bin/ngit/sub_commands/sync.rs b/src/bin/ngit/sub_commands/sync.rs
index 5578a0f..1a2d4ee 100644
--- a/src/bin/ngit/sub_commands/sync.rs
+++ b/src/bin/ngit/sub_commands/sync.rs
@@ -1,14 +1,19 @@
1use std::collections::HashMap;
2
1use anyhow::{Context, Result, bail}; 3use anyhow::{Context, Result, bail};
4use console::Term;
2use ngit::{ 5use ngit::{
3 client::{ 6 client::{
4 Client, Connect, Params, fetching_with_report, get_repo_ref_from_cache, 7 Client, Connect, Params, fetching_with_report, get_repo_ref_from_cache,
5 get_state_from_cache, 8 get_state_from_cache,
6 }, 9 },
7 git::{Repo, RepoActions}, 10 fetch::fetch_from_git_server,
11 git::{Repo, RepoActions, nostr_url::NostrUrlDecoded},
8 list::{get_ahead_behind, list_from_remotes}, 12 list::{get_ahead_behind, list_from_remotes},
9 push::push_to_remote, 13 push::push_to_remote,
10 repo_ref::get_repo_coordinates_when_remote_unknown, 14 repo_ref::{get_repo_coordinates_when_remote_unknown, is_grasp_server_clone_url},
11 utils::get_short_git_server_name, 15 repo_state::RepoState,
16 utils::{get_short_git_server_name, join_with_and},
12}; 17};
13 18
14#[derive(Debug, clap::Args)] 19#[derive(Debug, clap::Args)]
@@ -75,10 +80,13 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> {
75 &term, 80 &term,
76 &git_repo, 81 &git_repo,
77 &repo_ref.git_server, 82 &repo_ref.git_server,
78 &repo_ref.to_nostr_git_url(&None), 83 &decoded_nostr_url,
79 &repo_ref.grasp_servers(), 84 &repo_ref.grasp_servers(),
80 ); 85 );
81 86
87 let missing_refs =
88 fetch_missing_refs(&git_repo, &nostr_state, &remote_states, &decoded_nostr_url);
89
82 for (url, (remote_state, is_grasp_server)) in &remote_states { 90 for (url, (remote_state, is_grasp_server)) in &remote_states {
83 let remote_name = get_short_git_server_name(&git_repo, url); 91 let remote_name = get_short_git_server_name(&git_repo, url);
84 let mut refspecs = vec![]; 92 let mut refspecs = vec![];
@@ -116,6 +124,10 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> {
116 continue; 124 continue;
117 } 125 }
118 } 126 }
127 // skip refs missing locally
128 if missing_refs.contains(nostr_ref_name) {
129 continue;
130 }
119 if invalid_nostr_state_ref(nostr_ref_name) { 131 if invalid_nostr_state_ref(nostr_ref_name) {
120 // ensure nostr_state only supports refs/heads and refs/tags/ 132 // ensure nostr_state only supports refs/heads and refs/tags/
121 // and not refs/heads/prs/* 133 // and not refs/heads/prs/*
@@ -209,6 +221,12 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> {
209 } 221 }
210 } 222 }
211 223
224 if !missing_refs.is_empty() {
225 println!(
226 "skipped the following refs as could not find them locally or on any git servers: {}",
227 join_with_and(&missing_refs)
228 );
229 }
212 Ok(()) 230 Ok(())
213} 231}
214 232
@@ -216,3 +234,92 @@ fn invalid_nostr_state_ref(ref_name: &str) -> bool {
216 ref_name.starts_with("refs/heads/pr/") 234 ref_name.starts_with("refs/heads/pr/")
217 && !(ref_name.starts_with("refs/heads/") || ref_name.starts_with("refs/tags/")) 235 && !(ref_name.starts_with("refs/heads/") || ref_name.starts_with("refs/tags/"))
218} 236}
237
238fn identify_missing_refs(git_repo: &Repo, state: &HashMap<String, String>) -> Vec<String> {
239 let mut missing_oids = vec![];
240 for tip in state.values() {
241 if let Ok(exist) = git_repo.does_commit_exist(tip) {
242 if !exist {
243 missing_oids.push(tip.to_string());
244 }
245 }
246 }
247 missing_oids
248}
249
250/// returns refs that are still missing
251fn fetch_missing_refs(
252 git_repo: &Repo,
253 nostr_state: &RepoState,
254 remote_states: &HashMap<String, (HashMap<String, String>, bool)>,
255 nostr_url_decoded: &NostrUrlDecoded,
256) -> Vec<String> {
257 let mut tried_remotes: Vec<String> = vec![];
258 let required_oids = identify_missing_refs(git_repo, &nostr_state.state);
259 if !required_oids.is_empty() {
260 println!("fetching git data missing locally");
261 }
262 loop {
263 let required_oids = identify_missing_refs(git_repo, &nostr_state.state);
264 let mut oids_on_remote: HashMap<String, Vec<String>> = HashMap::new();
265 if !required_oids.is_empty() {
266 for (url, (state, _)) in remote_states {
267 if tried_remotes.contains(url) {
268 continue;
269 }
270 for oid in &required_oids {
271 if state.values().any(|v| v.eq(oid)) {
272 oids_on_remote
273 .entry(url.to_string())
274 .or_default()
275 .push(oid.clone());
276 }
277 }
278 }
279 }
280 if let Some((url, oids)) = oids_on_remote.iter().max_by_key(|(url, vec)| {
281 if tried_remotes.contains(url) {
282 0
283 } else {
284 vec.len()
285 }
286 }) {
287 if oids.is_empty() || tried_remotes.contains(url) {
288 break;
289 }
290 tried_remotes.push(url.clone());
291 let _ = fetch_from_git_server(
292 git_repo,
293 oids,
294 url,
295 nostr_url_decoded,
296 &Term::stdout(),
297 is_grasp_server_clone_url(url),
298 );
299 } else {
300 break;
301 }
302 }
303
304 let still_missing_oids = identify_missing_refs(git_repo, &nostr_state.state);
305 if still_missing_oids.is_empty() {
306 vec![]
307 } else {
308 let missing_refs: Vec<String> = nostr_state
309 .state
310 .iter()
311 .filter_map(|(key, value)| {
312 if still_missing_oids.contains(value) {
313 Some(key.clone())
314 } else {
315 None
316 }
317 })
318 .collect();
319 println!(
320 "could not find refs on repo git servers: {}",
321 join_with_and(&missing_refs)
322 );
323 missing_refs
324 }
325}