From 716adfab8b649f7cf55343539adb0e493fb23e9b Mon Sep 17 00:00:00 2001 From: kjuulh Date: Tue, 4 Mar 2025 13:11:37 +0100 Subject: [PATCH] feat: allow clone all --- Cargo.lock | 1 + crates/gitnow/Cargo.toml | 1 + crates/gitnow/src/commands.rs | 80 +++++++++++++++++++++++++++++++++++ crates/gitnow/src/main.rs | 6 ++- 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1bd5880..50bbef7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -815,6 +815,7 @@ dependencies = [ "prost", "prost-types", "ratatui", + "regex", "serde", "termwiz 0.23.0", "tokio", diff --git a/crates/gitnow/Cargo.toml b/crates/gitnow/Cargo.toml index 334ffe7..fdd09a3 100644 --- a/crates/gitnow/Cargo.toml +++ b/crates/gitnow/Cargo.toml @@ -36,6 +36,7 @@ ratatui = { version = "0.29.0", features = ["termwiz"] } crossterm = { version = "0.28.0", features = ["event-stream"] } futures = "0.3.30" termwiz = "0.23.0" +regex = "1.11.1" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/crates/gitnow/src/commands.rs b/crates/gitnow/src/commands.rs index d6de77d..8449fe2 100644 --- a/crates/gitnow/src/commands.rs +++ b/crates/gitnow/src/commands.rs @@ -1,3 +1,83 @@ pub mod root; pub mod shell; pub mod update; +pub mod clone { + + use std::sync::Arc; + + use futures::future::join_all; + use regex::Regex; + + use crate::{ + app::App, cache::CacheApp, custom_command::CustomCommandApp, git_clone::GitCloneApp, + }; + + #[derive(clap::Parser)] + pub struct CloneCommand { + #[arg(long = "search")] + search: String, + } + + impl CloneCommand { + pub async fn execute(&mut self, app: &'static App) -> anyhow::Result<()> { + let repos = app.cache().get().await?.ok_or(anyhow::anyhow!( + "failed to get cache, do a gitnow update first" + ))?; + + let search = Regex::new(&self.search)?; + + let filtered_repos = repos + .iter() + .filter(|r| search.is_match(&r.to_rel_path().display().to_string())) + .collect::>(); + + let concurrency_limit = Arc::new(tokio::sync::Semaphore::new(5)); + let mut handles = Vec::default(); + for repo in filtered_repos { + let config = app.config.clone(); + let custom_command = app.custom_command(); + let git_clone = app.git_clone(); + let repo = repo.clone(); + let concurrency = Arc::clone(&concurrency_limit); + + let handle = tokio::spawn(async move { + let permit = concurrency.acquire().await?; + + let project_path = config.settings.projects.directory.join(repo.to_rel_path()); + if !project_path.exists() { + eprintln!("cloning repository: {}", repo.to_rel_path().display()); + git_clone.clone_repo(&repo, false).await?; + + custom_command + .execute_post_clone_command(&project_path) + .await?; + } + + drop(permit); + + Ok::<(), anyhow::Error>(()) + }); + + handles.push(handle); + } + + let res = join_all(handles).await; + + for res in res { + match res { + Ok(Ok(())) => {} + Ok(Err(e)) => { + tracing::error!("failed to clone repo: {}", e); + anyhow::bail!(e) + } + Err(e) => { + tracing::error!("failed to clone repo: {}", e); + anyhow::bail!(e) + } + } + } + + Ok(()) + } + } +} diff --git a/crates/gitnow/src/main.rs b/crates/gitnow/src/main.rs index cd7f057..e00392e 100644 --- a/crates/gitnow/src/main.rs +++ b/crates/gitnow/src/main.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use anyhow::Context; use clap::{Parser, Subcommand}; -use commands::{root::RootCommand, shell::Shell, update::Update}; +use commands::{clone::CloneCommand, root::RootCommand, shell::Shell, update::Update}; use config::Config; use tracing::level_filters::LevelFilter; use tracing_subscriber::EnvFilter; @@ -52,6 +52,7 @@ struct Command { enum Commands { Init(Shell), Update(Update), + Clone(CloneCommand), } const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml"; @@ -89,6 +90,9 @@ async fn main() -> anyhow::Result<()> { Commands::Update(mut update) => { update.execute(app).await?; } + Commands::Clone(mut clone) => { + clone.execute(app).await?; + } }, None => { RootCommand::new(app)