upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin/git_remote_nostr/push.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/git_remote_nostr/push.rs')
-rw-r--r--src/bin/git_remote_nostr/push.rs212
1 files changed, 171 insertions, 41 deletions
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs
index 0a7f1a6..71afc98 100644
--- a/src/bin/git_remote_nostr/push.rs
+++ b/src/bin/git_remote_nostr/push.rs
@@ -2,6 +2,8 @@ use core::str;
2use std::{ 2use std::{
3 collections::{HashMap, HashSet}, 3 collections::{HashMap, HashSet},
4 io::Stdin, 4 io::Stdin,
5 sync::{Arc, Mutex},
6 time::Instant,
5}; 7};
6 8
7use anyhow::{anyhow, bail, Context, Result}; 9use anyhow::{anyhow, bail, Context, Result};
@@ -39,7 +41,7 @@ use crate::{
39 utils::{ 41 utils::{
40 find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url, 42 find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url,
41 get_short_git_server_name, get_write_protocols_to_try, join_with_and, 43 get_short_git_server_name, get_write_protocols_to_try, join_with_and,
42 push_error_is_not_authentication_failure, read_line, report_on_sideband_progress, 44 push_error_is_not_authentication_failure, read_line,
43 }, 45 },
44}; 46};
45 47
@@ -404,25 +406,35 @@ fn push_to_remote_url(
404 let auth = GitAuthenticator::default(); 406 let auth = GitAuthenticator::default();
405 let mut push_options = git2::PushOptions::new(); 407 let mut push_options = git2::PushOptions::new();
406 let mut remote_callbacks = git2::RemoteCallbacks::new(); 408 let mut remote_callbacks = git2::RemoteCallbacks::new();
409 let push_reporter = Arc::new(Mutex::new(PushReporter::new(term)));
410
407 remote_callbacks.credentials(auth.credentials(&git_config)); 411 remote_callbacks.credentials(auth.credentials(&git_config));
408 remote_callbacks.push_update_reference(|name, error| { 412
409 if let Some(error) = error { 413 remote_callbacks.push_update_reference({
410 term.write_line( 414 let push_reporter = Arc::clone(&push_reporter);
411 format!( 415 move |name, error| {
416 let mut reporter = push_reporter.lock().unwrap();
417 if let Some(error) = error {
418 let existing_lines = reporter.count_all_existing_lines();
419 reporter.update_reference_errors.push(format!(
412 "WARNING: {} failed to push {name} error: {error}", 420 "WARNING: {} failed to push {name} error: {error}",
413 get_short_git_server_name(git_repo, git_server_url), 421 get_short_git_server_name(git_repo, git_server_url),
414 ) 422 ));
415 .as_str(), 423 reporter.write_all(existing_lines);
416 ) 424 }
417 .unwrap(); 425 Ok(())
418 } 426 }
419 Ok(())
420 }); 427 });
421 remote_callbacks.push_negotiation(|updates| { 428
422 for update in updates { 429 remote_callbacks.push_negotiation({
423 let _ = term.write_line( 430 let push_reporter = Arc::clone(&push_reporter);
424 format!( 431 move |updates| {
425 " {}..{} {} -> {}", 432 let mut reporter = push_reporter.lock().unwrap();
433 let existing_lines = reporter.count_all_existing_lines();
434
435 for update in updates {
436 let msg = format!(
437 "push: {}..{} {} -> {}",
426 oid_to_shorthand_string(update.dst()).unwrap(), 438 oid_to_shorthand_string(update.dst()).unwrap(),
427 oid_to_shorthand_string(update.src()).unwrap(), 439 oid_to_shorthand_string(update.src()).unwrap(),
428 update 440 update
@@ -435,36 +447,30 @@ fn push_to_remote_url(
435 .unwrap_or("") 447 .unwrap_or("")
436 .replace("refs/heads/", "") 448 .replace("refs/heads/", "")
437 .replace("refs/tags/", "tags/"), 449 .replace("refs/tags/", "tags/"),
438 ) 450 );
439 .as_str(), 451 reporter.negotiation.push(msg);
440 ); 452 }
441 // now a blank line for transfer progress to delete 453 reporter.write_all(existing_lines);
442 let _ = term.write_line(""); 454 Ok(())
443 } 455 }
444 Ok(())
445 }); 456 });
446 remote_callbacks.push_transfer_progress( 457
458 remote_callbacks.push_transfer_progress({
459 let push_reporter = Arc::clone(&push_reporter);
447 #[allow(clippy::cast_precision_loss)] 460 #[allow(clippy::cast_precision_loss)]
448 |current, total, bytes| { 461 move |current, total, bytes| {
449 let _ = term.clear_last_lines(1); 462 let mut reporter = push_reporter.lock().unwrap();
450 let percentage = (current as f64 / total as f64) * 100.0; 463 reporter.process_transfer_progress_update(current, total, bytes);
451 let (size, unit) = if bytes >= (1024 * 1024) { 464 }
452 (bytes as f64 / (1024.0 * 1024.0), "MiB") 465 });
453 } else {
454 (bytes as f64 / 1024.0, "KiB")
455 };
456 let _ = term.write_line(
457 format!(
458 "Writing objects: {percentage:.0}% ({current}/{total}) {size:.2} {unit}, done."
459 )
460 .as_str(),
461 );
462 },
463 );
464 466
465 remote_callbacks.sideband_progress(|data| { 467 remote_callbacks.sideband_progress({
466 report_on_sideband_progress(data, term); 468 let push_reporter = Arc::clone(&push_reporter);
467 true 469 move |data| {
470 let mut reporter = push_reporter.lock().unwrap();
471 reporter.process_remote_msg(data);
472 true
473 }
468 }); 474 });
469 push_options.remote_callbacks(remote_callbacks); 475 push_options.remote_callbacks(remote_callbacks);
470 git_server_remote.push(remote_refspecs, Some(&mut push_options))?; 476 git_server_remote.push(remote_refspecs, Some(&mut push_options))?;
@@ -472,6 +478,130 @@ fn push_to_remote_url(
472 Ok(()) 478 Ok(())
473} 479}
474 480
481#[allow(clippy::cast_precision_loss)]
482#[allow(clippy::float_cmp)]
483#[allow(clippy::needless_pass_by_value)]
484fn report_on_transfer_progress(
485 current: usize,
486 total: usize,
487 bytes: usize,
488 start_time: &Instant,
489 end_time: &Option<Instant>,
490) -> String {
491 let percentage = ((current as f64 / total as f64) * 100.0)
492 // always round down because 100% complete is misleading when its not complete
493 .floor();
494 let (size, unit) = if bytes as f64 >= (1024.0 * 1024.0) {
495 (bytes as f64 / (1024.0 * 1024.0), "MiB")
496 } else {
497 (bytes as f64 / 1024.0, "KiB")
498 };
499 let speed = {
500 let duration = if let Some(end_time) = end_time {
501 (*end_time - *start_time).as_millis() as f64
502 } else {
503 start_time.elapsed().as_millis() as f64
504 };
505
506 if duration > 0.0 {
507 (bytes as f64 / (1024.0 * 1024.0)) / (duration / 1000.0) // Convert bytes to MiB and milliseconds to seconds
508 } else {
509 0.0
510 }
511 };
512
513 format!(
514 "push: Writing objects: {percentage}% ({current}/{total}) {size:.2} {unit} | {speed:.2} MiB/s{}",
515 if current == total { ", done." } else { "" },
516 )
517}
518
519struct PushReporter<'a> {
520 remote_msgs: Vec<String>,
521 negotiation: Vec<String>,
522 transfer_progress_msgs: Vec<String>,
523 update_reference_errors: Vec<String>,
524 term: &'a console::Term,
525 start_time: Option<Instant>,
526 end_time: Option<Instant>,
527}
528impl<'a> PushReporter<'a> {
529 fn new(term: &'a console::Term) -> Self {
530 Self {
531 remote_msgs: vec![],
532 negotiation: vec![],
533 transfer_progress_msgs: vec![],
534 update_reference_errors: vec![],
535 term,
536 start_time: None,
537 end_time: None,
538 }
539 }
540 fn write_all(&self, lines_to_clear: usize) {
541 let _ = self.term.clear_last_lines(lines_to_clear);
542 for msg in &self.remote_msgs {
543 let _ = self.term.write_line(msg);
544 }
545 for msg in &self.negotiation {
546 let _ = self.term.write_line(msg);
547 }
548 for msg in &self.transfer_progress_msgs {
549 let _ = self.term.write_line(msg);
550 }
551 for msg in &self.update_reference_errors {
552 let _ = self.term.write_line(msg);
553 }
554 }
555
556 fn count_all_existing_lines(&self) -> usize {
557 self.remote_msgs.len()
558 + self.negotiation.len()
559 + self.transfer_progress_msgs.len()
560 + self.update_reference_errors.len()
561 }
562 fn process_remote_msg(&mut self, data: &[u8]) {
563 let existing_lines = self.count_all_existing_lines();
564 if let Ok(data) = str::from_utf8(data) {
565 let data = data
566 .split(['\n', '\r'])
567 .find(|line| !line.is_empty())
568 .unwrap_or("")
569 .trim();
570 if !data.is_empty() {
571 let msg = format!("remote: {data}");
572 if let Some(last) = self.remote_msgs.last() {
573 if (last.contains('%') && !last.contains("100%"))
574 || last == &msg.replace(", done.", "")
575 {
576 self.remote_msgs.pop();
577 }
578 }
579 self.remote_msgs.push(msg);
580 self.write_all(existing_lines);
581 }
582 }
583 }
584 fn process_transfer_progress_update(&mut self, current: usize, total: usize, bytes: usize) {
585 if self.start_time.is_none() {
586 self.start_time = Some(Instant::now());
587 }
588 let existing_lines = self.count_all_existing_lines();
589
590 let report = report_on_transfer_progress(
591 current,
592 total,
593 bytes,
594 &self.start_time.unwrap(),
595 &self.end_time,
596 );
597 if report.contains("100%") {
598 self.end_time = Some(Instant::now());
599 }
600 self.transfer_progress_msgs = vec![report];
601 self.write_all(existing_lines);
602 }
603}
604
475type HashMapUrlRefspecs = HashMap<String, Vec<String>>; 605type HashMapUrlRefspecs = HashMap<String, Vec<String>>;
476 606
477#[allow(clippy::too_many_lines)] 607#[allow(clippy::too_many_lines)]