refactor: move walker to thread

This commit is contained in:
2026-02-02 20:12:16 +01:00
parent b7f8ae5098
commit 44e2818d47
3 changed files with 134 additions and 88 deletions

36
Cargo.lock generated
View File

@@ -74,8 +74,6 @@ dependencies = [
"libc", "libc",
"libgit2-sys", "libgit2-sys",
"log", "log",
"openssl-probe",
"openssl-sys",
"url", "url",
] ]
@@ -212,26 +210,10 @@ checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
"libssh2-sys",
"libz-sys", "libz-sys",
"openssl-sys",
"pkg-config", "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]] [[package]]
name = "libz-sys" name = "libz-sys"
version = "1.1.23" version = "1.1.23"
@@ -256,24 +238,6 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 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]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.2" version = "2.3.2"

View File

@@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
git2 = "0.20.3" git2 = { version = "0.20.3", features = ["vendored-libgit2"], default-features = false }

View File

@@ -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; 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) => { ($e:expr) => {
match $e { match $e {
Ok(val) => val, Ok(val) => val,
Err(e) => { Err(e) => {
eprintln!("error: {e}"); eprintln!("error: {e}");
continue; 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() { fn main() {
@@ -20,56 +61,97 @@ fn main() {
None => env::current_dir(), None => env::current_dir(),
}; };
for repo in find_git_repo(&dir.unwrap()).unwrap() { let dir = ok_or_bail!(dir);
println!("{}", repo.path().parent().unwrap().display());
let worktrees = continue_if_error!(repo.worktrees()); 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() { for worktree in worktrees.iter() {
let worktree = repo.find_worktree(worktree.unwrap()).unwrap(); 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<Repository>) -> 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.
// <https://bford.info/cachedir/>
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<Vec<Repository>> { fn is_cache_dir(path: &Path) -> io::Result<bool> {
if !path.exists() { let cache_dir_tag = path.join(CACHE_DIR_TAG);
todo!(); if !cache_dir_tag.is_file() {
return Ok(false);
} }
if path.is_file() { let mut buf = [0u8; CACHE_DIR_TAG_HEADER.len()];
todo!(); let _ = File::open(cache_dir_tag)?.read(&mut buf)?;
}
let mut r = Vec::new(); Ok(CACHE_DIR_TAG_HEADER == buf)
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)
} }