From 44e2818d47f87c4738577b845f9b75730010cc1b Mon Sep 17 00:00:00 2001 From: Jonas Kattendick Date: Mon, 2 Feb 2026 20:12:16 +0100 Subject: [PATCH] refactor: move walker to thread --- Cargo.lock | 36 ---------- Cargo.toml | 2 +- src/main.rs | 184 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 134 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55f12d6..0c131ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,8 +74,6 @@ dependencies = [ "libc", "libgit2-sys", "log", - "openssl-probe", - "openssl-sys", "url", ] @@ -212,26 +210,10 @@ checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", - "libssh2-sys", "libz-sys", - "openssl-sys", "pkg-config", ] -[[package]] -name = "libssh2-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - [[package]] name = "libz-sys" version = "1.1.23" @@ -256,24 +238,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "percent-encoding" version = "2.3.2" diff --git a/Cargo.toml b/Cargo.toml index 67f2387..5f8ee4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] -git2 = "0.20.3" +git2 = { version = "0.20.3", features = ["vendored-libgit2"], default-features = false } diff --git a/src/main.rs b/src/main.rs index 319ee7c..f69eb3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,58 @@ -use std::{env, io, path::{Path, PathBuf}}; +use std::{ + env, + fs::File, + io::{self, Read}, + path::{Path, PathBuf}, + sync::mpsc, + thread, +}; use git2::Repository; -macro_rules! continue_if_error { +const GIT_DIR: &str = ".git"; +const CACHE_DIR_TAG: &str = "CACHEDIR.TAG"; +const CACHE_DIR_TAG_HEADER: &[u8] = b"Signature: 8a477f597d28d172789f06886806bc55"; + +macro_rules! ok_or_continue { ($e:expr) => { match $e { - Ok(val) => val, - Err(e) => { - eprintln!("error: {e}"); - continue; + Ok(val) => val, + Err(e) => { + eprintln!("error: {e}"); + continue; + } } - } - } + }; +} + +macro_rules! ok_or_bail { + ($e:expr) => { + match $e { + Ok(val) => val, + Err(e) => { + eprintln!("error: {e}"); + std::process::exit(1); + } + } + }; +} + +macro_rules! io_error { + ($kind:ident) => { + Err(std::io::Error::new( + std::io::ErrorKind::$kind, + format!("{}", std::io::ErrorKind::$kind), + )) + }; + ($kind:ident, $prefix:expr) => { + Err(io::Error::new( + std::io::ErrorKind::$kind, + format!("{}: {}", $prefix, std::io::ErrorKind::$kind), + )) + }; + ($kind:expr, $prefix:expr) => { + Err(io::Error::new($kind, format!("{}: {}", $prefix, $kind))) + }; } fn main() { @@ -20,56 +61,97 @@ fn main() { None => env::current_dir(), }; - for repo in find_git_repo(&dir.unwrap()).unwrap() { - println!("{}", repo.path().parent().unwrap().display()); - let worktrees = continue_if_error!(repo.worktrees()); + let dir = ok_or_bail!(dir); + + let walker = Walker { + ignore_hidden: true, + ignore_caches: true, + }; + + let (tx, rx) = mpsc::channel(); + let join_handle = thread::spawn(move || walker.walk(&dir, tx)); + + for repo in rx { + let repo_path = repo.path().parent().unwrap().display(); + + println!("{repo_path}"); + + let worktrees = ok_or_continue!(repo.worktrees()); for worktree in worktrees.iter() { let worktree = repo.find_worktree(worktree.unwrap()).unwrap(); - println!("{}", worktree.path().display()); + let worktree_path = worktree.path().display(); + println!("{worktree_path}"); } } + + ok_or_bail!(join_handle.join().expect("thead paniced")); +} + +struct Walker { + ignore_hidden: bool, + ignore_caches: bool, +} + +impl Walker { + fn walk(&self, path: &Path, tx: mpsc::Sender) -> io::Result<()> { + if !path.exists() { + return io_error!(NotFound, path.display()); + } + + if path.is_file() { + return io_error!(NotADirectory, path.display()); + } + + let read_dir = match path.read_dir() { + Ok(read_dir) => read_dir, + Err(e) => return io_error!(e.kind(), path.display()), + }; + + for entry in read_dir { + let entry = ok_or_continue!(entry); + let file_type = ok_or_continue!(entry.file_type()); + + if !file_type.is_dir() { + continue; + } + + let entry_path = entry.path(); + + if self.ignore_hidden && entry.file_name().as_encoded_bytes().first() == Some(&b'.') { + // Skip "hidden" dot-prefixed files. + continue; + } + + if self.ignore_caches && ok_or_continue!(is_cache_dir(&entry_path)) { + // Ignore directories that contain a valid CACHEDIR.TAG file. + // + continue; + } + + let git_dir = entry_path.join(GIT_DIR); + + if !git_dir.is_dir() { + // Descend deeper into directories that are not git repositories. + ok_or_continue!(self.walk(&entry_path, tx.clone())); + continue; + } + + tx.send(ok_or_continue!(Repository::open(git_dir))) + .expect("main thread paniced"); + } + + Ok(()) + } } -fn find_git_repo(path: &Path) -> io::Result> { - if !path.exists() { - todo!(); +fn is_cache_dir(path: &Path) -> io::Result { + let cache_dir_tag = path.join(CACHE_DIR_TAG); + if !cache_dir_tag.is_file() { + return Ok(false); } - if path.is_file() { - todo!(); - } + let mut buf = [0u8; CACHE_DIR_TAG_HEADER.len()]; + let _ = File::open(cache_dir_tag)?.read(&mut buf)?; - let mut r = Vec::new(); - - for entry in path.read_dir()? { - let entry = continue_if_error!(entry); - let file_type = continue_if_error!(entry.file_type()); - - if !file_type.is_dir() { - continue; - } - - let entry_path = entry.path(); - - if entry.file_name().as_encoded_bytes().get(0) == Some(&b'.') { - continue; - } - - let cache_tag = entry_path.join("CACHEDIR.TAG"); - if cache_tag.exists() { - continue; - } - - let git_dir = entry_path.join(".git"); - if !git_dir.is_dir() { - r.extend(continue_if_error!(find_git_repo(&entry_path))); - continue; - } - - let repo = continue_if_error!(Repository::open(git_dir)); - - r.push(repo); - } - - Ok(r) + Ok(CACHE_DIR_TAG_HEADER == buf) }