Skip to content

Commit

Permalink
clone-repo: display clone progress
Browse files Browse the repository at this point in the history
closes #137
  • Loading branch information
junglerobba committed Dec 7, 2024
1 parent 164c609 commit 61f7723
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 33 deletions.
37 changes: 4 additions & 33 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use std::{
collections::HashMap,
env::current_dir,
fs::canonicalize,
path::{Path, PathBuf},
};
use std::{collections::HashMap, env::current_dir, fs::canonicalize, path::PathBuf};

use crate::{
clone::git_clone,
configs::{Config, SearchDirectory, SessionSortOrderConfig},
dirty_paths::DirtyUtf8Path,
execute_command, get_single_selection,
Expand All @@ -18,7 +14,7 @@ use crate::{
use clap::{Args, Parser, Subcommand};
use clap_complete::{ArgValueCandidates, CompletionCandidate};
use error_stack::ResultExt;
use git2::{build::RepoBuilder, FetchOptions, RemoteCallbacks, Repository};
use git2::Repository;
use ratatui::style::Color;

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -654,6 +650,7 @@ fn clone_repo_command(args: &CloneRepoCommand, config: Config, tmux: &Tmux) -> R
let repo_name = repo_name.trim_end_matches(".git");
path.push(repo_name);

println!("Cloning into '{repo_name}'...");
let repo = git_clone(&args.repository, &path)?;

let mut session_name = repo_name.to_string();
Expand All @@ -677,32 +674,6 @@ fn clone_repo_command(args: &CloneRepoCommand, config: Config, tmux: &Tmux) -> R
Ok(())
}

fn git_clone(repo: &str, target: &Path) -> Result<Repository> {
let mut callbacks = RemoteCallbacks::new();
callbacks.credentials(git_credentials_callback);
let mut fo = FetchOptions::new();
fo.remote_callbacks(callbacks);
let mut builder = RepoBuilder::new();
builder.fetch_options(fo);

builder
.clone(repo, target)
.change_context(TmsError::GitError)
}

fn git_credentials_callback(
user: &str,
user_from_url: Option<&str>,
_cred: git2::CredentialType,
) -> std::result::Result<git2::Cred, git2::Error> {
let user = match user_from_url {
Some(user) => user,
None => user,
};

git2::Cred::ssh_key_from_agent(user)
}

fn init_repo_command(args: &InitRepoCommand, config: Config, tmux: &Tmux) -> Result<()> {
let Some(mut path) = pick_search_path(&config, tmux)? else {
return Ok(());
Expand Down
159 changes: 159 additions & 0 deletions src/clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use std::{
fmt::Display,
io::{stdout, Stdout, Write},
path::Path,
time::{Duration, Instant},
};

use crate::{error::TmsError, Result};

use crossterm::{cursor, terminal, ExecutableCommand};
use error_stack::ResultExt;
use git2::{build::RepoBuilder, FetchOptions, Progress, RemoteCallbacks, Repository};

const UPDATE_INTERVAL: Duration = Duration::from_millis(300);

struct Rate(usize);

impl Display for Rate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0 > 1024 * 1024 {
let rate = self.0 as f64 / 1024.0 / 1024.0;
write!(f, "{:.2} MB/s", rate)
} else {
let rate = self.0 as f64 / 1024.0;
write!(f, "{:.2} kB/s", rate)
}
}
}

struct CloneSnapshot {
time: Instant,
bytes_transferred: usize,
stdout: Stdout,
lines: u16,
}

impl CloneSnapshot {
pub fn new() -> Self {
let stdout = stdout();
Self {
time: Instant::now(),
bytes_transferred: 0,
stdout,
lines: 0,
}
}

pub fn update(&mut self, progress: &Progress) -> Result<()> {
let now = Instant::now();
let difference = now.duration_since(self.time);
if difference < UPDATE_INTERVAL {
return Ok(());
}

let transferred = progress.received_bytes() - self.bytes_transferred;
let rate = Rate(transferred / (difference.as_millis() as usize) * 1000);

let network_pct = (100 * progress.received_objects()) / progress.total_objects();
let index_pct = (100 * progress.indexed_objects()) / progress.total_objects();

let total = (network_pct + index_pct) / 2;

if self.lines > 0 {
self.stdout
.execute(cursor::MoveUp(self.lines))
.change_context(TmsError::IoError)?;
self.stdout
.execute(terminal::Clear(terminal::ClearType::FromCursorDown))
.change_context(TmsError::IoError)?;
}

let mut lines = 0;

if network_pct < 100 {
writeln!(
self.stdout,
"Received {:3}% ({:5}/{:5})",
network_pct,
progress.received_objects(),
progress.total_objects(),
)
.change_context(TmsError::IoError)?;
lines += 1
}

if index_pct < 100 {
writeln!(
self.stdout,
"Indexed {:3}% ({:5}/{:5})",
index_pct,
progress.indexed_objects(),
progress.total_objects(),
)
.change_context(TmsError::IoError)?;
lines += 1;
}

if network_pct < 100 {
writeln!(self.stdout, "{} ", rate).change_context(TmsError::IoError)?;
lines += 1;
}

if progress.total_objects() > 0 && progress.received_objects() == progress.total_objects() {
let delta_pct = (100 * progress.indexed_deltas()) / progress.total_deltas();
writeln!(
self.stdout,
"Resolving deltas {:3}% ({:5}/{:5})",
delta_pct,
progress.indexed_deltas(),
progress.total_deltas()
)
.change_context(TmsError::IoError)?;
lines += 1;
}
write!(self.stdout, "{:3}% ", total).change_context(TmsError::IoError)?;
for _ in 0..(total / 3) {
write!(self.stdout, "█").change_context(TmsError::IoError)?;
}
writeln!(self.stdout).change_context(TmsError::IoError)?;
lines += 1;
self.time = Instant::now();
self.bytes_transferred = progress.received_bytes();
self.lines = lines;

Ok(())
}
}

pub fn git_clone(repo: &str, target: &Path) -> Result<Repository> {
let mut callbacks = RemoteCallbacks::new();
callbacks.credentials(git_credentials_callback);

let mut state = CloneSnapshot::new();
callbacks.transfer_progress(move |progress| {
state.update(&progress).ok();
true
});
let mut fo = FetchOptions::new();
fo.remote_callbacks(callbacks);
let mut builder = RepoBuilder::new();
builder.fetch_options(fo);

builder
.clone(repo, target)
.change_context(TmsError::GitError)
}

fn git_credentials_callback(
user: &str,
user_from_url: Option<&str>,
_cred: git2::CredentialType,
) -> std::result::Result<git2::Cred, git2::Error> {
let user = match user_from_url {
Some(user) => user,
None => user,
};

git2::Cred::ssh_key_from_agent(user)
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod cli;
mod clone;
pub mod configs;
pub mod dirty_paths;
pub mod error;
Expand Down

0 comments on commit 61f7723

Please sign in to comment.