diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8b0ceab30..f377fce51 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -301,6 +301,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -464,6 +474,7 @@ version = "0.1.0" dependencies = [ "fix-path-env", "git2", + "ignore", "serde", "serde_json", "tauri", @@ -578,6 +589,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1370,6 +1400,19 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -1733,6 +1776,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ff9a618e7..1ad88c5b3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -28,6 +28,7 @@ uuid = { version = "1", features = ["v4"] } tauri-plugin-dialog = "2" git2 = "0.20.3" fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" } +ignore = "0.4.25" [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] tauri-plugin-updater = "2" diff --git a/src-tauri/src/workspaces.rs b/src-tauri/src/workspaces.rs index f871d7a8d..992287875 100644 --- a/src-tauri/src/workspaces.rs +++ b/src-tauri/src/workspaces.rs @@ -1,6 +1,7 @@ use std::io::Write; use std::path::PathBuf; +use ignore::WalkBuilder; use tauri::{AppHandle, State}; use tokio::process::Command; use uuid::Uuid; @@ -30,44 +31,34 @@ fn sanitize_worktree_name(branch: &str) -> String { } } -fn should_skip_dir(name: &str) -> bool { - matches!( - name, - ".git" | "node_modules" | "dist" | "target" | "release-artifacts" - ) -} - fn list_workspace_files_inner(root: &PathBuf, max_files: usize) -> Vec { let mut results = Vec::new(); - let mut stack = vec![root.clone()]; - - while let Some(dir) = stack.pop() { - let entries = match std::fs::read_dir(&dir) { - Ok(entries) => entries, + let walker = WalkBuilder::new(root) + // Allow hidden entries. + .hidden(false) + // Follow symlinks to search their contents. + .follow_links(true) + // Don't require git to be present to apply to apply git-related ignore rules. + .require_git(false) + .build(); + + for entry in walker { + let entry = match entry { + Ok(entry) => entry, Err(_) => continue, }; - for entry in entries.flatten() { - let path = entry.path(); - let file_name = entry.file_name().to_string_lossy().to_string(); - if path.is_dir() { - if should_skip_dir(&file_name) { - continue; - } - stack.push(path); - continue; - } - if path.is_file() { - if let Ok(rel_path) = path.strip_prefix(root) { - let normalized = normalize_git_path(&rel_path.to_string_lossy()); - if !normalized.is_empty() { - results.push(normalized); - } - } - } - if results.len() >= max_files { - return results; + if !entry.file_type().is_some_and(|ft| ft.is_file()) { + continue; + } + if let Ok(rel_path) = entry.path().strip_prefix(root) { + let normalized = normalize_git_path(&rel_path.to_string_lossy()); + if !normalized.is_empty() { + results.push(normalized); } } + if results.len() >= max_files { + break; + } } results.sort();